spring boot - Rewrite internal eureka based links to external links in zuul proxy -


i writing microservice based application spring-boot services.

for communication use rest (with hateoas links). each service registers eureka, links provide based on these names, ribbon enhanced resttemplates can use loadbalancing , failover capabilities of stack.

this works fine internal communication, have single page admin app accesses services through zuul based reverse proxy. when links using real hostname , port links correctly rewritten match url visible outside. of course doesn't work symbolic links need in inside...

so internally have links like:

http://adminusers/myfunnyusername 

the zuul proxy should rewrite to

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

is there missing in zuul or somewhere along way make easier?

right i'm thinking how reliably rewrite urls myself without collateral damage.

there should simpler way, right?

aparrently zuul not capable of rewriting links symbolic eureka names "outside links".

for wrote zuul filter parses json response, , looks "links" nodes , rewrites links schema.

for example, services named: adminusers , restaurants result service has links http://adminusers/{id} , http://restaurants/cuisine/{id}

then rewritten http://localhost:8090/api/adminusers/{id} , http://localhost:8090/api/restaurants/cuisine/{id}

private string fixlink(string href) {     //right "real" links contain ports , 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 optimized little, compile regexp once, i'll once i'm sure need in long run)

update

thomas asked full filter code, here is. aware, makes assumptions urls! assume internal links not contain port , have servicename host, valid assumption eureka based apps, ribbon etc. able work those. rewrite link $proxy/api/$servicename/... feel free use 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 "real" links contain ports , 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;         (mediatype supportedtype : supportedtypes) {             if (supportedtype.iscompatiblewith(getresponsemediatype(context))) return true;         }         return false;     }      private static mediatype getresponsemediatype(final requestcontext context) {         assert context != null;         (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 character encoding of stream (depends on response content type)?                 responsedata = charstreams.tostring(new inputstreamreader(responsedatastream));             }         }         return responsedata;     }      private void traverse(map<string, object> node) {         (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) {         (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) {         (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 welcome :-)


Comments

Popular posts from this blog

angularjs - ADAL JS Angular- WebAPI add a new role claim to the token -

php - CakePHP HttpSockets send array of paramms -

node.js - Using Node without global install -