Skip to content

Commit

Permalink
Adds the RewritePlugin. Closes #926 (#933)
Browse files Browse the repository at this point in the history
  • Loading branch information
waldekmastykarz authored Nov 12, 2024
1 parent eec04e6 commit 5d5b3a7
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 2 deletions.
3 changes: 2 additions & 1 deletion dev-proxy-abstractions/IProxyLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public enum MessageType
Mocked,
InterceptedResponse,
FinishedProcessingRequest,
Skipped
Skipped,
Processed
}

public class LoggingContext(SessionEventArgs session)
Expand Down
93 changes: 93 additions & 0 deletions dev-proxy-plugins/Processing/RewritePlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.DevProxy.Abstractions;
using System.Text.RegularExpressions;

namespace Microsoft.DevProxy.Plugins.Processing;

public class RewriteRule
{
public string? Url { get; set; }
}

public class RequestRewrite
{
public RewriteRule? In { get; set; }
public RewriteRule? Out { get; set; }
}

public class RewritePluginConfiguration
{
public IEnumerable<RequestRewrite> Rewrites { get; set; } = [];
public string RewritesFile { get; set; } = "rewrites.json";
}

public class RewritePlugin(IPluginEvents pluginEvents, IProxyContext context, ILogger logger, ISet<UrlToWatch> urlsToWatch, IConfigurationSection? configSection = null) : BaseProxyPlugin(pluginEvents, context, logger, urlsToWatch, configSection)
{
public override string Name => nameof(RewritePlugin);
private readonly RewritePluginConfiguration _configuration = new();
private RewritesLoader? _loader = null;

public override async Task RegisterAsync()
{
await base.RegisterAsync();

ConfigSection?.Bind(_configuration);
_loader = new RewritesLoader(Logger, _configuration);

PluginEvents.BeforeRequest += BeforeRequestAsync;

// make the rewrites file path relative to the configuration file
_configuration.RewritesFile = Path.GetFullPath(
ProxyUtils.ReplacePathTokens(_configuration.RewritesFile),
Path.GetDirectoryName(Context.Configuration.ConfigFile ?? string.Empty) ?? string.Empty
);

_loader?.InitResponsesWatcher();
}

private Task BeforeRequestAsync(object sender, ProxyRequestArgs e)
{
if (UrlsToWatch is null ||
!e.HasRequestUrlMatch(UrlsToWatch))
{
Logger.LogRequest("URL not matched", MessageType.Skipped, new LoggingContext(e.Session));
return Task.CompletedTask;
}

if (_configuration.Rewrites is null ||
!_configuration.Rewrites.Any())
{
Logger.LogRequest("No rewrites configured", MessageType.Skipped, new LoggingContext(e.Session));
return Task.CompletedTask;
}

var request = e.Session.HttpClient.Request;

foreach (var rewrite in _configuration.Rewrites)
{
if (string.IsNullOrEmpty(rewrite.In?.Url) ||
string.IsNullOrEmpty(rewrite.Out?.Url))
{
continue;
}

var newUrl = Regex.Replace(request.Url, rewrite.In.Url, rewrite.Out.Url, RegexOptions.IgnoreCase);

if (request.Url.Equals(newUrl, StringComparison.OrdinalIgnoreCase))
{
Logger.LogRequest($"{rewrite.In?.Url}", MessageType.Skipped, new LoggingContext(e.Session));
}
else
{
Logger.LogRequest($"{rewrite.In?.Url} > {newUrl}", MessageType.Processed, new LoggingContext(e.Session));
request.Url = newUrl;
}
}

return Task.CompletedTask;
}
}
87 changes: 87 additions & 0 deletions dev-proxy-plugins/Processing/RewritesLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.DevProxy.Abstractions;
using Microsoft.Extensions.Logging;
using System.Text.Json;

namespace Microsoft.DevProxy.Plugins.Processing;

internal class RewritesLoader(ILogger logger, RewritePluginConfiguration configuration) : IDisposable
{
private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger));
private readonly RewritePluginConfiguration _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));

private string RewritesFilePath => _configuration.RewritesFile;
private FileSystemWatcher? _watcher;

public void LoadRewrites()
{
if (!File.Exists(RewritesFilePath))
{
_logger.LogWarning("File {configurationFile} not found. No rewrites will be provided", _configuration.RewritesFile);
_configuration.Rewrites = [];
return;
}

try
{
using var stream = new FileStream(RewritesFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using var reader = new StreamReader(stream);
var RewritesString = reader.ReadToEnd();
var rewritesConfig = JsonSerializer.Deserialize<RewritePluginConfiguration>(RewritesString, ProxyUtils.JsonSerializerOptions);
IEnumerable<RequestRewrite>? configRewrites = rewritesConfig?.Rewrites;
if (configRewrites is not null)
{
_configuration.Rewrites = configRewrites;
_logger.LogInformation("Rewrites for {configResponseCount} url patterns loaded from {RewritesFile}", configRewrites.Count(), _configuration.RewritesFile);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error has occurred while reading {RewritesFile}:", _configuration.RewritesFile);
}
}

public void InitResponsesWatcher()
{
if (_watcher is not null)
{
return;
}

string path = Path.GetDirectoryName(RewritesFilePath) ?? throw new InvalidOperationException($"{RewritesFilePath} is an invalid path");
if (!File.Exists(RewritesFilePath))
{
_logger.LogWarning("File {RewritesFile} not found. No rewrites will be provided", _configuration.RewritesFile);
_configuration.Rewrites = [];
return;
}

_watcher = new FileSystemWatcher(Path.GetFullPath(path))
{
NotifyFilter = NotifyFilters.CreationTime
| NotifyFilters.FileName
| NotifyFilters.LastWrite
| NotifyFilters.Size,
Filter = Path.GetFileName(RewritesFilePath)
};
_watcher.Changed += ResponsesFile_Changed;
_watcher.Created += ResponsesFile_Changed;
_watcher.Deleted += ResponsesFile_Changed;
_watcher.Renamed += ResponsesFile_Changed;
_watcher.EnableRaisingEvents = true;

LoadRewrites();
}

private void ResponsesFile_Changed(object sender, FileSystemEventArgs e)
{
LoadRewrites();
}

public void Dispose()
{
_watcher?.Dispose();
}
}
4 changes: 3 additions & 1 deletion dev-proxy/Logging/ProxyConsoleFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,14 @@ private static string GetMessageTypeString(MessageType messageType)
{
MessageType.InterceptedRequest => "req",
MessageType.InterceptedResponse => "res",
MessageType.PassedThrough => "api",
MessageType.PassedThrough => "pass",
MessageType.Chaos => "oops",
MessageType.Warning => "warn",
MessageType.Mocked => "mock",
MessageType.Failed => "fail",
MessageType.Tip => "tip",
MessageType.Skipped => "skip",
MessageType.Processed => "proc",
_ => " "
};
}
Expand All @@ -251,6 +252,7 @@ private static (ConsoleColor bg, ConsoleColor fg) GetMessageTypeColor(MessageTyp
MessageType.InterceptedRequest => (bgColor, ConsoleColor.Gray),
MessageType.PassedThrough => (ConsoleColor.Gray, fgColor),
MessageType.Skipped => (bgColor, ConsoleColor.Gray),
MessageType.Processed => (ConsoleColor.DarkGreen, fgColor),
MessageType.Chaos => (ConsoleColor.DarkRed, fgColor),
MessageType.Warning => (ConsoleColor.DarkYellow, fgColor),
MessageType.Mocked => (ConsoleColor.DarkMagenta, fgColor),
Expand Down
44 changes: 44 additions & 0 deletions schemas/v0.23.0/rewriteplugin.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Dev Proxy RewritePlugin rewrite rules",
"description": "Rewrite rules for the Dev Proxy RewritePlugin",
"type": "object",
"properties": {
"$schema": {
"type": "string"
},
"rewrites": {
"type": "array",
"items": {
"type": "object",
"properties": {
"in": {
"type": "object",
"properties": {
"url": {
"type": "string",
"pattern": "^.+$"
}
},
"required": ["url"]
},
"out": {
"type": "object",
"properties": {
"url": {
"type": "string",
"pattern": "^.*$"
}
},
"required": ["url"]
}
},
"required": ["in", "out"]
}
}
},
"required": [
"rewrites"
],
"additionalProperties": false
}

0 comments on commit 5d5b3a7

Please sign in to comment.