spring cloud gateway通過註冊中心(eureka)聚合swagger

在spring cloud 的使用的時候,我發現測試起來很不方便,需要使用Postman等類似的工具來調用我們的接口,這顯然是很麻煩的,那麼有沒有一種方式可以讓我們在gateway裏使用swagger來測試呢。本文基於Finchley.RELEASE和最新版的Finchley.SR2,這兩個版本有所改動,後面介紹。
答案是肯定的,我查閱資料發現了之前有人實現了zuul網關的聚合swagger,通過他的思路我自己寫了一些類,首先需要,在gateway網關中創建三個類,下面貼出來

SwaggerHandler

package com.e6yun.ms.config;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Mono;

import springfox.documentation.swagger.web.*;

import java.util.Optional;

/**

 * @Description

 * @Author [email protected]

 * @Created Date: 2018/8/16 11:52

 * @ClassName SwaggerHandler

 * @Version: 1.0

 */

@RestController

@RequestMapping("/swagger-resources")

public class SwaggerHandler {

    @Autowired(required = false)

    private SecurityConfiguration securityConfiguration;

    @Autowired(required = false)

    private UiConfiguration uiConfiguration;

    private final SwaggerResourcesProvider swaggerResources;

    @Autowired

    public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {

        this.swaggerResources = swaggerResources;

    }

    @GetMapping("/configuration/security")

    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {

        return Mono.just(new ResponseEntity<>(

                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));

    }

    @GetMapping("/configuration/ui")

    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {

        return Mono.just(new ResponseEntity<>(

                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));

    }

    @GetMapping("")

    public Mono<ResponseEntity> swaggerResources() {

        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));

    }

}

SwaggerHeaderFilter 這個類只是在Finchley.RELEASE版本需要實現,SR2版本無需實現。


package com.e6yun.ms.config;

import org.apache.commons.lang3.StringUtils;

import org.springframework.cloud.gateway.filter.GatewayFilter;

import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;

import org.springframework.http.server.reactive.ServerHttpRequest;

import org.springframework.stereotype.Component;

import org.springframework.web.server.ServerWebExchange;

/**

 * @Description

 * @Author [email protected]

 * @Created Date: 2018/8/16 12:29

 * @ClassName SwaggerHeaderFilter

 * @Version: 1.0

 */

@Component

public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {

    private static final String HEADER_NAME = "X-Forwarded-Prefix";

    private static final String HOST_NAME = "X-Forwarded-Host";

    @Override

    public GatewayFilter apply(Object config) {

        return (exchange, chain) -> {

            ServerHttpRequest request = exchange.getRequest();

            String path = request.getURI().getPath();

            if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {

                return chain.filter(exchange);

            }

            String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));

            ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();

            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();

            return chain.filter(newExchange);

        };

    }

}


package com.e6yun.ms.config;

import org.springframework.cloud.gateway.config.GatewayProperties;

import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;

import org.springframework.cloud.gateway.route.RouteLocator;

import org.springframework.context.annotation.Primary;

import org.springframework.stereotype.Component;

import springfox.documentation.swagger.web.SwaggerResource;

import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;

import java.util.List;
最後就是我們能從註冊中心發現服務的地方了 SwaggerProvider這個類可以從註冊中心拉取服務列表 我們通過配置 將註冊服務的路由轉換成swagger-api

/**

 * @Description

 * @Author [email protected]

 * @Created Date: 2018/8/15 16:04

 * @ClassName SwaggerProvider

 * @Version: 1.0

 */

@Component

@Primary

public class SwaggerProvider implements SwaggerResourcesProvider {

    public static final String API_URI = "/v2/api-docs";

    public static final String EUREKA_SUB_PRIX = "CompositeDiscoveryClient_";

    private final DiscoveryClientRouteDefinitionLocator routeLocator;

    public SwaggerProvider(DiscoveryClientRouteDefinitionLocator routeLocator) {

        this.routeLocator = routeLocator;

    }

    @Override

    public List<SwaggerResource> get() {

        List<SwaggerResource> resources = new ArrayList<>();

        List<String> routes = new ArrayList<>();

        //從DiscoveryClientRouteDefinitionLocator 中取出routes,構造成swaggerResource

        routeLocator.getRouteDefinitions().subscribe(routeDefinition -> {

          resources.add(swaggerResource(routeDefinition.getId().substring(EUREKA_SUB_PRIX.length()),routeDefinition.getPredicates().get(0).getArgs().get("pattern").replace("/**", API_URI)));

        });

        return resources;

    }

    private SwaggerResource swaggerResource(String name, String location) {

        SwaggerResource swaggerResource = new SwaggerResource();

        swaggerResource.setName(name);

        swaggerResource.setLocation(location);

        swaggerResource.setSwaggerVersion("2.0");

        return swaggerResource;

    }

}

這樣寫完後,頁面就可以發現註冊到eureka的服務了。

這時,由於我們使用的是服務發現的routes,我們寫的SwaggerHeaderFilter 不再生效了,所以這裏訪問會丟失服務名,這時我們需要在配置文件中添加一條語句,這裏追加一個default-filters即可。SR2版本無需寫


spring.cloud.gateway.default-filters[0]=SwaggerHeaderFilter

我這裏還重寫了spring cloud gateway的ForwardedHeadersFilter這是由於我們使用的swagger版本是2.6.1,新版的代碼中它修復了這個bug 它裏面的源碼有一處是這麼寫的


package springfox.documentation.swagger2.web;

import javax.servlet.http.HttpServletRequest;

import org.springframework.util.StringUtils;

import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import org.springframework.web.util.UriComponents;

public class HostNameProvider {

    public HostNameProvider() {

        throw new UnsupportedOperationException();

    }

    static UriComponents componentsFrom(HttpServletRequest request) {

        ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromServletMapping(request);

        ForwardedHeader forwarded = ForwardedHeader.of(request.getHeader(ForwardedHeader.NAME));

        String proto = StringUtils.hasText(forwarded.getProto()) ? forwarded.getProto() : request.getHeader("X-Forwarded-Proto");

        String forwardedSsl = request.getHeader("X-Forwarded-Ssl");

        if (StringUtils.hasText(proto)) {

            builder.scheme(proto);

        } else if (StringUtils.hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on")) {

            builder.scheme("https");

        }

        String host = forwarded.getHost();

        host = StringUtils.hasText(host) ? host : request.getHeader("X-Forwarded-Host");

        if (!StringUtils.hasText(host)) {

            return builder.build();

        } else {

            String[] hosts = StringUtils.commaDelimitedListToStringArray(host);

            String hostToUse = hosts[0];

            if (hostToUse.contains(":")) {

                String[] hostAndPort = StringUtils.split(hostToUse, ":");

                builder.host(hostAndPort[0]);

                builder.port(Integer.parseInt(hostAndPort[1]));

            } else {

                builder.host(hostToUse);

                builder.port(-1);

            }

            String port = request.getHeader("X-Forwarded-Port");

            if (StringUtils.hasText(port)) {

                // 這裏他寫了對post轉int的操作,但是gateway傳入的port是一個String類型的,導致轉換異常

                builder.port(Integer.parseInt(port));

            }

            return builder.build();

        }

    }

}

爲此我們有兩種方案:

1.重寫這個類

2.重寫gateway中傳過來port的類

我最終選擇了2號方案,原因是我們做的這個聚合swagger gateway只是用來做開發測試使用,所以這個gateway和我們正式的gateway不是一個東西,但是你去重寫了swagger的源碼,將會導致所有的服務的swagger源碼都被修改,沒有必要。所以我重寫了ForwardedHeadersFilter


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

import org.springframework.core.Ordered;

import org.springframework.http.HttpHeaders;

import org.springframework.http.server.reactive.ServerHttpRequest;

import org.springframework.stereotype.Component;

import org.springframework.util.CollectionUtils;

import org.springframework.util.LinkedCaseInsensitiveMap;

import org.springframework.util.ObjectUtils;

import org.springframework.util.StringUtils;

import org.springframework.web.server.ServerWebExchange;

import java.net.InetSocketAddress;

import java.net.URI;

import java.util.*;

/**

 * @Description

 * @Author [email protected]

 * @Created Date: 2018/8/16 17:14

 * @ClassName ForwardedHeadersFilter

 * @Version: 1.0

 */

@Component

public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered {

    public static final String FORWARDED_HEADER = "Forwarded";

    public ForwardedHeadersFilter() {

    }

    @Override

    public int getOrder() {

        return 0;

    }

    @Override

    public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {

        ServerHttpRequest request = exchange.getRequest();

        HttpHeaders updated = new HttpHeaders();

        input.entrySet().stream().filter((entry) -> {

            return !((String)entry.getKey()).toLowerCase().equalsIgnoreCase("Forwarded");

        }).forEach((entry) -> {

            updated.addAll((String)entry.getKey(), (List)entry.getValue());

        });

        List<ForwardedHeadersFilter.Forwarded> forwardeds = parse(input.get("Forwarded"));

        Iterator var7 = forwardeds.iterator();

        while(var7.hasNext()) {

            ForwardedHeadersFilter.Forwarded f = (ForwardedHeadersFilter.Forwarded)var7.next();

            updated.add("Forwarded", f.toString());

        }

        URI uri = request.getURI();

        String host = input.getFirst("Host");

        ForwardedHeadersFilter.Forwarded forwarded = (new ForwardedHeadersFilter.Forwarded()).put("host", host).put("proto", uri.getScheme());

        InetSocketAddress remoteAddress = request.getRemoteAddress();

        //改了這裏

        if (remoteAddress != null) {

            String forValue = remoteAddress.getAddress().getHostAddress();

            int port = remoteAddress.getPort();

            if (port >= 0) {

                forValue = forValue + ":" + port;

            }

            forwarded.put("for", forValue);

        }

        updated.add("Forwarded", forwarded.toHeaderValue());

        return updated;

    }

    static List<ForwardedHeadersFilter.Forwarded> parse(List<String> values) {

        ArrayList<ForwardedHeadersFilter.Forwarded> forwardeds = new ArrayList();

        if (CollectionUtils.isEmpty(values)) {

            return forwardeds;

        } else {

            Iterator var2 = values.iterator();

            while(var2.hasNext()) {

                String value = (String)var2.next();

                ForwardedHeadersFilter.Forwarded forwarded = parse(value);

                forwardeds.add(forwarded);

            }

            return forwardeds;

        }

    }

    static ForwardedHeadersFilter.Forwarded parse(String value) {

        String[] pairs = StringUtils.tokenizeToStringArray(value, ";");

        LinkedCaseInsensitiveMap<String> result = splitIntoCaseInsensitiveMap(pairs);

        if (result == null) {

            return null;

        } else {

            ForwardedHeadersFilter.Forwarded forwarded = new ForwardedHeadersFilter.Forwarded(result);

            return forwarded;

        }

    }

    static LinkedCaseInsensitiveMap<String> splitIntoCaseInsensitiveMap(String[] pairs) {

        if (ObjectUtils.isEmpty(pairs)) {

            return null;

        } else {

            LinkedCaseInsensitiveMap<String> result = new LinkedCaseInsensitiveMap();

            String[] var2 = pairs;

            int var3 = pairs.length;

            for(int var4 = 0; var4 < var3; ++var4) {

                String element = var2[var4];

                String[] splittedElement = StringUtils.split(element, "=");

                if (splittedElement != null) {

                    result.put(splittedElement[0].trim(), splittedElement[1].trim());

                }

            }

            return result;

        }

    }

    static class Forwarded {

        private static final char EQUALS = '=';

        private static final char SEMICOLON = ';';

        private final Map<String, String> values;

        public Forwarded() {

            this.values = new HashMap();

        }

        public Forwarded(Map<String, String> values) {

            this.values = values;

        }

        public ForwardedHeadersFilter.Forwarded put(String key, String value) {

            this.values.put(key, value);

            return this;

        }

        private String quoteIfNeeded(String s) {

            return s != null && s.contains(":") ? "\"" + s + "\"" : s;

        }

        public String get(String key) {

            return (String)this.values.get(key);

        }

        Map<String, String> getValues() {

            return this.values;

        }

        public String toString() {

            return "Forwarded{values=" + this.values + '}';

        }

        public String toHeaderValue() {

            StringBuilder builder = new StringBuilder();

            Map.Entry entry;

            for(Iterator var2 = this.values.entrySet().iterator(); var2.hasNext(); builder.append((String)entry.getKey()).append('=').append((String)entry.getValue())) {

                entry = (Map.Entry)var2.next();

                if (builder.length() > 0) {

                    builder.append(';');

                }

            }

            return builder.toString();

        }

    }

}

這樣傳入的值就是能被正確的解析爲int類型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章