-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
1 parent
eec04e6
commit 5d5b3a7
Showing
5 changed files
with
229 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |