Idea "Mask Urls" for Url transformation in response #1821
Replies: 6 comments 9 replies
-
On a related note, there are cases where a service would still have to know its public endpoint that the client used to reach it through the Gateway (Ocelot). A common mechanism is that the gateway would add the non-standardized headers Popular packages such as Swashbuckle would magically just work behind Ocelot if the |
Beta Was this translation helpful? Give feedback.
-
@dimitridaras Ocelot can already transform the Location header, see the docs here. @dimitridaras @davidni I agree this would be a good feature, one day I might get round to it but for now I don't have time :(. I am so busy with work, life and just fixing bugs and answering questions. At the moment my main focus is just to improve the code quality of Ocelot and make it more modular. A lot of the code isn't very good and this was mainly because when I started the project I was just excited to have users requesting features so a lot of features were created without much though for other scenarios (such as the one requested here). However let's keep this open and we can do it one day. |
Beta Was this translation helpful? Give feedback.
-
I am still quite new to Ocelot. In the meantime, what is the best workaround? A delegating handler? |
Beta Was this translation helpful? Give feedback.
-
@dimitridaras you can either use a delegating handler, inject middleware or just a middleware before the Ocelot pipeline, I think you can manipulate the HttpContext object Ocelot will return. Though I'm not 100% sure of that. |
Beta Was this translation helpful? Give feedback.
-
Hi @dimitridaras ! |
Beta Was this translation helpful? Give feedback.
-
For those who look for workaround, here is something to start with internal sealed class UrlRewriteDelegatingHandler : DelegatingHandler
{
private static readonly JsonSerializerOptions SerializerOptions = CreateSerializerOptions();
private readonly IHttpContextAccessor _httpContextAccessor;
public UrlRewriteDelegatingHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
using var responseMessage = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
var responseStream = await responseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using var responseStreamDisposable = responseStream.ConfigureAwait(false);
var dictionary = await JsonSerializer.DeserializeAsync<IDictionary<string, object?>>(responseStream, SerializerOptions, cancellationToken).ConfigureAwait(false);
if (dictionary is not null)
{
RewriteDictionary(dictionary);
}
var newResponseStream = new MemoryStream();
await JsonSerializer.SerializeAsync(newResponseStream, dictionary, SerializerOptions, cancellationToken).ConfigureAwait(false);
newResponseStream.Seek(0, SeekOrigin.Begin);
var newResponseMessage = new HttpResponseMessage(responseMessage.StatusCode)
{
ReasonPhrase = responseMessage.ReasonPhrase,
Version = responseMessage.Version,
};
foreach (var (key, value) in responseMessage.Headers)
{
newResponseMessage.Headers.TryAddWithoutValidation(key, value);
}
newResponseMessage.Content = new StreamContent(newResponseStream);
foreach (var (key, value) in responseMessage.Content.Headers)
{
newResponseMessage.Content.Headers.TryAddWithoutValidation(key, value);
}
return newResponseMessage;
}
private static JsonSerializerOptions CreateSerializerOptions()
{
var options = new JsonSerializerOptions();
options.Converters.Add(new JsonDictionaryConverterFactory());
return options;
}
private static string MakePathRegex(string pathTemplate)
{
return pathTemplate
.Replace("{", "(?<", StringComparison.OrdinalIgnoreCase)
.Replace("}", ">.+)", StringComparison.OrdinalIgnoreCase);
}
private void RewriteDictionary(IDictionary<string, object?> dictionary)
{
foreach (var (key, value) in dictionary)
{
if (value is string url && Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
dictionary[key] = RewriteUrl(uri);
}
else if (value is IDictionary<string, object?> nested)
{
RewriteDictionary(nested);
}
}
}
private string RewriteUrl(Uri uri)
{
var route = FindMatchingRoute(uri);
if (route is null)
{
return uri.ToString();
}
var pathTemplate = MakePathRegex(route.DownstreamPathTemplate.Value);
var match = Regex.Match(uri.PathAndQuery, pathTemplate);
var groups = ((IEnumerable<Group>)match.Groups).Skip(1).Select(group => (group.Name, group.Value));
var upstreamTemplate = route.UpstreamPathTemplate.OriginalValue;
foreach (var (name, value) in groups)
{
upstreamTemplate = upstreamTemplate.Replace($"{{{name}}}", value, StringComparison.OrdinalIgnoreCase);
}
var request = _httpContextAccessor.HttpContext?.Request ?? throw new InvalidOperationException("No request present.");
var uriBuilder = new UriBuilder(
request.Scheme,
request.Host.Host);
if (request.Host.Port.HasValue)
{
uriBuilder.Port = request.Host.Port.Value;
}
uriBuilder.Path = upstreamTemplate;
return uriBuilder.ToString();
}
private DownstreamRoute? FindMatchingRoute(Uri uri)
{
var configuration = _httpContextAccessor.HttpContext?.Items.IInternalConfiguration();
if (configuration is null)
{
return null;
}
var routes = configuration.Routes;
foreach (var route in routes)
{
foreach (var downstreamRoute in route.DownstreamRoute)
{
if (MatchesRoute(downstreamRoute, uri))
{
return downstreamRoute;
}
}
}
return null;
static bool MatchesRoute(DownstreamRoute routeToMatch, Uri uriToMatch)
{
var uriPath = uriToMatch.PathAndQuery;
var pathTemplate = MakePathRegex(routeToMatch.DownstreamPathTemplate.Value);
return Regex.IsMatch(uriPath, pathTemplate);
}
}
} |
Beta Was this translation helpful? Give feedback.
-
New Feature
It would be great if Ocelot could include built-in support for URL transformation in response bodies and the location header (called, for instance, Mask URLs in content in Transformation | Azure API Management). I.e., a downstream service URL in the response is converted to the upstream gateway URL.
Motivation for New Feature
Restful APIs should include HATEOAS - useful links to other endpoints within the response. Most people agree that these should be absolute. The service only knows about itself - it shouldn't know anything about the gateway, but the client should only know about the gateway; therefore the gateway needs to convert service URLs to gateway URLs.
Beta Was this translation helpful? Give feedback.
All reactions