在zuul proxy中重写基于内部尤里卡的链接到外部链接(Rewrite internal eureka based links to external links in zuul proxy)

编程入门 行业动态 更新时间:2024-10-11 11:20:36
在zuul proxy中重写基于内部尤里卡的链接到外部链接(Rewrite internal eureka based links to external links in zuul proxy)

我正在用spring-boot服务编写一个基于微服务的应用程序。

对于沟通,我使用REST(与讨厌的链接)。 每个服务都向eureka注册,所以我提供的链接都基于这些名称,以便功能增强的resttemplates可以使用堆栈的负载平衡和故障转移功能。

这适用于内部沟通,但我有一个页面管理员应用程序,通过基于zuul的反向代理访问服务。 当链接使用真实的主机名和端口时,链接被正确地重写以匹配从外部可见的URL。 这当然不适用于我在内部需要的符号链接...

所以内部我有像这样的链接:

http://adminusers/myfunnyusername

zuul代理应该重写这个

http://localhost:8090/api/adminusers/myfunnyusername

有没有什么我在zuul或沿途的某个地方可能会让这更容易?

现在我正在考虑如何可靠地重写URL,而不会造成附带损害。

应该有一个更简单的方法,对吧?

I am writing a microservice based application with spring-boot services.

For communication I use REST (with hateoas links). Each service registers with eureka, so I the links I provide are based on these names, so that the ribbon enhanced resttemplates can use the loadbalancing and failover capabilities of the stack.

This works fine for internal communication, but I have a single page admin app that accesses the services through a zuul based reverse proxy. When the links are using the real hostname and port the links are correctly rewritten to match the url visible from the outside. This of course doesn't work for the symbolic links that I need in the inside...

So internally I have links like:

http://adminusers/myfunnyusername

The zuul proxy should rewrite this to

http://localhost:8090/api/adminusers/myfunnyusername

Is there something that I am missing in zuul or somewhere along the way that would make this easier?

Right now I'm thinking how to reliably rewrite the urls myself without collateral damage.

There should be a simpler way, right?

最满意答案

奇怪的是,Zuul并没有能力重写从象征性的尤里卡名字到“外部链接”的链接。

为此,我编写了一个Zuul过滤器来分析json响应,并查找“链接”节点并将链接重写为我的模式。

例如,我的服务被命名为:adminusers和restaurants服务的结果具有http:// adminusers / {id}和http:// restaurants / cuisine / {id}等链接。

然后它会被重写为http:// localhost:8090 / api / adminusers / {id}和http:// localhost:8090 / api / restaurants / cuisine / {id}

private String fixLink(String href) {
    //Right now all "real" links contain ports and loadbalanced links not
    //TODO: precompile regexes
    if (!href.matches("http[s]{0,1}://[a-zA-Z0-9]+:[0-9]+.*")) {
        String newRef = href.replaceAll("http[s]{0,1}://([a-zA-Z0-9]+)", BasicLinkBuilder.linkToCurrentMapping().toString() + "/api/$1");
        LOG.info("OLD: {}", href);
        LOG.info("NEW: {}", newRef);
        href = newRef;
    }
    return href;
}
 

(这需要稍微优化,因为您只能编译一次正则表达式,一旦我确定这是我真正需要的,从长远来看,我会这样做)

UPDATE

托马斯要求提供完整的过滤器代码,所以在这里。 请注意,它对网址做了一些假设! 我假设内部链接不包含端口并且具有作为主机的服务名称,这对于基于尤里卡的应用程序来说是有效的假设,因为功能区等能够与这些应用程序一起工作。 我将其重写为$ PROXY / api / $ SERVICENAME /等链接...请随意使用此代码。

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.CharStreams;
import com.netflix.util.Pair;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.hateoas.mvc.BasicLinkBuilder;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkNotNull;

@Component
public final class ContentUrlRewritingFilter extends ZuulFilter {
    private static final Logger LOG = LoggerFactory.getLogger(ContentUrlRewritingFilter.class);

    private static final String CONTENT_TYPE = "Content-Type";

    private static final ImmutableSet<MediaType> DEFAULT_SUPPORTED_TYPES = ImmutableSet.of(MediaType.APPLICATION_JSON);

    private final String replacement;
    private final ImmutableSet<MediaType> supportedTypes;
    //Right now all "real" links contain ports and loadbalanced links not
    private final Pattern detectPattern = Pattern.compile("http[s]{0,1}://[a-zA-Z0-9]+:[0-9]+.*");
    private final Pattern replacePattern;

    public ContentUrlRewritingFilter() {
        this.replacement = checkNotNull("/api/$1");
        this.supportedTypes = ImmutableSet.copyOf(checkNotNull(DEFAULT_SUPPORTED_TYPES));
        replacePattern = Pattern.compile("http[s]{0,1}://([a-zA-Z0-9]+)");
    }

    private static boolean containsContent(final RequestContext context) {
        assert context != null;
        return context.getResponseDataStream() != null || context.getResponseBody() != null;
    }

    private static boolean supportsType(final RequestContext context, final Collection<MediaType> supportedTypes) {
        assert supportedTypes != null;
        for (MediaType supportedType : supportedTypes) {
            if (supportedType.isCompatibleWith(getResponseMediaType(context))) return true;
        }
        return false;
    }

    private static MediaType getResponseMediaType(final RequestContext context) {
        assert context != null;
        for (final Pair<String, String> header : context.getZuulResponseHeaders()) {
            if (header.first().equalsIgnoreCase(CONTENT_TYPE)) {
                return MediaType.parseMediaType(header.second());
            }
        }
        return MediaType.APPLICATION_OCTET_STREAM;
    }

    @Override
    public String filterType() {
        return "post";
    }

    @Override
    public int filterOrder() {
        return 100;
    }

    @Override
    public boolean shouldFilter() {
        final RequestContext context = RequestContext.getCurrentContext();
        return hasSupportedBody(context);
    }

    public boolean hasSupportedBody(RequestContext context) {
        return containsContent(context) && supportsType(context, this.supportedTypes);
    }

    @Override
    public Object run() {
        try {
            rewriteContent(RequestContext.getCurrentContext());
        } catch (final Exception e) {
            Throwables.propagate(e);
        }
        return null;
    }

    private void rewriteContent(final RequestContext context) throws Exception {
        assert context != null;
        String responseBody = getResponseBody(context);
        if (responseBody != null) {
            ObjectMapper mapper = new ObjectMapper();
            LinkedHashMap<String, Object> map = mapper.readValue(responseBody, LinkedHashMap.class);
            traverse(map);
            String body = mapper.writeValueAsString(map);
            context.setResponseBody(body);
        }
    }

    private String getResponseBody(RequestContext context) throws IOException {
        String responseData = null;
        if (context.getResponseBody() != null) {
            context.getResponse().setCharacterEncoding("UTF-8");
            responseData = context.getResponseBody();

        } else if (context.getResponseDataStream() != null) {
            context.getResponse().setCharacterEncoding("UTF-8");
            try (final InputStream responseDataStream = context.getResponseDataStream()) {
                //FIXME What about character encoding of the stream (depends on the response content type)?
                responseData = CharStreams.toString(new InputStreamReader(responseDataStream));
            }
        }
        return responseData;
    }

    private void traverse(Map<String, Object> node) {
        for (Map.Entry<String, Object> entry : node.entrySet()) {
            if (entry.getKey().equalsIgnoreCase("links") && entry.getValue() instanceof Collection) {
                replaceLinks((Collection<Map<String, String>>) entry.getValue());
            } else {
                if (entry.getValue() instanceof Collection) {
                    traverse((Collection) entry.getValue());
                } else if (entry.getValue() instanceof Map) {
                    traverse((Map<String, Object>) entry.getValue());
                }
            }
        }
    }

    private void traverse(Collection<Map> value) {
        for (Object entry : value) {
            if (entry instanceof Collection) {
                traverse((Collection) entry);
            } else if (entry instanceof Map) {
                traverse((Map<String, Object>) entry);
            }
        }
    }

    private void replaceLinks(Collection<Map<String, String>> value) {
        for (Map<String, String> node : value) {
            if (node.containsKey("href")) {
                node.put("href", fixLink(node.get("href")));
            } else {
                LOG.debug("Link Node did not contain href! {}", value.toString());
            }
        }
    }

    private String fixLink(String href) {
        if (!detectPattern.matcher(href).matches()) {
            href = replacePattern.matcher(href).replaceAll(BasicLinkBuilder.linkToCurrentMapping().toString() + replacement);
        }
        return href;
    }
}
 

欢迎改进:-)

Aparrently Zuul is not capable of rewriting links from the symbolic eureka names to "outside links".

For that I just wrote a Zuul filter that parses the json response, and looks for "links" nodes and rewrites the links to my schema.

For example, my services are named: adminusers and restaurants The result from the service has links like http://adminusers/{id} and http://restaurants/cuisine/{id}

Then it would be rewritten to http://localhost:8090/api/adminusers/{id} and http://localhost:8090/api/restaurants/cuisine/{id}

private String fixLink(String href) {
    //Right now all "real" links contain ports and loadbalanced links not
    //TODO: precompile regexes
    if (!href.matches("http[s]{0,1}://[a-zA-Z0-9]+:[0-9]+.*")) {
        String newRef = href.replaceAll("http[s]{0,1}://([a-zA-Z0-9]+)", BasicLinkBuilder.linkToCurrentMapping().toString() + "/api/$1");
        LOG.info("OLD: {}", href);
        LOG.info("NEW: {}", newRef);
        href = newRef;
    }
    return href;
}
 

(This needs to be optimized a little, as you could compile the regexp only once, I'll do that once I'm sure that this is what I really need in the long run)

UPDATE

Thomas asked for the full filter code, so here it is. Be aware, it makes some assumptions about the URLs! I assume that internal links do not contain a port and have the servicename as host, which is a valid assumption for eureka based apps, as ribbon etc. are able to work with those. I rewrite that to a link like $PROXY/api/$SERVICENAME/... Feel free to use this code.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.CharStreams;
import com.netflix.util.Pair;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.hateoas.mvc.BasicLinkBuilder;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkNotNull;

@Component
public final class ContentUrlRewritingFilter extends ZuulFilter {
    private static final Logger LOG = LoggerFactory.getLogger(ContentUrlRewritingFilter.class);

    private static final String CONTENT_TYPE = "Content-Type";

    private static final ImmutableSet<MediaType> DEFAULT_SUPPORTED_TYPES = ImmutableSet.of(MediaType.APPLICATION_JSON);

    private final String replacement;
    private final ImmutableSet<MediaType> supportedTypes;
    //Right now all "real" links contain ports and loadbalanced links not
    private final Pattern detectPattern = Pattern.compile("http[s]{0,1}://[a-zA-Z0-9]+:[0-9]+.*");
    private final Pattern replacePattern;

    public ContentUrlRewritingFilter() {
        this.replacement = checkNotNull("/api/$1");
        this.supportedTypes = ImmutableSet.copyOf(checkNotNull(DEFAULT_SUPPORTED_TYPES));
        replacePattern = Pattern.compile("http[s]{0,1}://([a-zA-Z0-9]+)");
    }

    private static boolean containsContent(final RequestContext context) {
        assert context != null;
        return context.getResponseDataStream() != null || context.getResponseBody() != null;
    }

    private static boolean supportsType(final RequestContext context, final Collection<MediaType> supportedTypes) {
        assert supportedTypes != null;
        for (MediaType supportedType : supportedTypes) {
            if (supportedType.isCompatibleWith(getResponseMediaType(context))) return true;
        }
        return false;
    }

    private static MediaType getResponseMediaType(final RequestContext context) {
        assert context != null;
        for (final Pair<String, String> header : context.getZuulResponseHeaders()) {
            if (header.first().equalsIgnoreCase(CONTENT_TYPE)) {
                return MediaType.parseMediaType(header.second());
            }
        }
        return MediaType.APPLICATION_OCTET_STREAM;
    }

    @Override
    public String filterType() {
        return "post";
    }

    @Override
    public int filterOrder() {
        return 100;
    }

    @Override
    public boolean shouldFilter() {
        final RequestContext context = RequestContext.getCurrentContext();
        return hasSupportedBody(context);
    }

    public boolean hasSupportedBody(RequestContext context) {
        return containsContent(context) && supportsType(context, this.supportedTypes);
    }

    @Override
    public Object run() {
        try {
            rewriteContent(RequestContext.getCurrentContext());
        } catch (final Exception e) {
            Throwables.propagate(e);
        }
        return null;
    }

    private void rewriteContent(final RequestContext context) throws Exception {
        assert context != null;
        String responseBody = getResponseBody(context);
        if (responseBody != null) {
            ObjectMapper mapper = new ObjectMapper();
            LinkedHashMap<String, Object> map = mapper.readValue(responseBody, LinkedHashMap.class);
            traverse(map);
            String body = mapper.writeValueAsString(map);
            context.setResponseBody(body);
        }
    }

    private String getResponseBody(RequestContext context) throws IOException {
        String responseData = null;
        if (context.getResponseBody() != null) {
            context.getResponse().setCharacterEncoding("UTF-8");
            responseData = context.getResponseBody();

        } else if (context.getResponseDataStream() != null) {
            context.getResponse().setCharacterEncoding("UTF-8");
            try (final InputStream responseDataStream = context.getResponseDataStream()) {
                //FIXME What about character encoding of the stream (depends on the response content type)?
                responseData = CharStreams.toString(new InputStreamReader(responseDataStream));
            }
        }
        return responseData;
    }

    private void traverse(Map<String, Object> node) {
        for (Map.Entry<String, Object> entry : node.entrySet()) {
            if (entry.getKey().equalsIgnoreCase("links") && entry.getValue() instanceof Collection) {
                replaceLinks((Collection<Map<String, String>>) entry.getValue());
            } else {
                if (entry.getValue() instanceof Collection) {
                    traverse((Collection) entry.getValue());
                } else if (entry.getValue() instanceof Map) {
                    traverse((Map<String, Object>) entry.getValue());
                }
            }
        }
    }

    private void traverse(Collection<Map> value) {
        for (Object entry : value) {
            if (entry instanceof Collection) {
                traverse((Collection) entry);
            } else if (entry instanceof Map) {
                traverse((Map<String, Object>) entry);
            }
        }
    }

    private void replaceLinks(Collection<Map<String, String>> value) {
        for (Map<String, String> node : value) {
            if (node.containsKey("href")) {
                node.put("href", fixLink(node.get("href")));
            } else {
                LOG.debug("Link Node did not contain href! {}", value.toString());
            }
        }
    }

    private String fixLink(String href) {
        if (!detectPattern.matcher(href).matches()) {
            href = replacePattern.matcher(href).replaceAll(BasicLinkBuilder.linkToCurrentMapping().toString() + replacement);
        }
        return href;
    }
}
 

Improvements are welcome :-)

更多推荐

本文发布于:2023-07-26 06:29:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1271990.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:尤里   链接   重写   zuul   proxy

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!