Added ability to change priority of rewrite rules.

This commit is contained in:
Jimmy Campbell 2017-08-01 17:40:57 -07:00
Родитель 4164b66874
Коммит ee7dda429e
12 изменённых файлов: 341 добавлений и 41 удалений

Просмотреть файл

@ -151,6 +151,9 @@ namespace Microsoft.IIS.Administration.Core.Utils {
catch (FileLoadException e) {
throw new LockedException(model.Path, e);
}
catch (ApiArgumentException) {
throw;
}
catch (Exception e) {
if (model is JValue) {
throw new ApiArgumentException(model.Path, e);
@ -173,6 +176,9 @@ namespace Microsoft.IIS.Administration.Core.Utils {
catch (FileLoadException e) {
throw new LockedException(model.Path, e);
}
catch (ApiArgumentException) {
throw;
}
catch (Exception e) {
if (model is JValue) {
throw new ApiArgumentException(model.Path, e);
@ -195,6 +201,9 @@ namespace Microsoft.IIS.Administration.Core.Utils {
catch (FileLoadException e) {
throw new LockedException(model.Path, e);
}
catch (ApiArgumentException) {
throw;
}
catch (Exception e) {
if (model is JValue) {
throw new ApiArgumentException(model.Path, e);

Просмотреть файл

@ -4,6 +4,8 @@
namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
{
using System;
sealed class InboundRuleCollection : RuleCollectionBase {
protected override void CopyInfo(RuleElement source, RuleElement destination) {

Просмотреть файл

@ -4,6 +4,8 @@
namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
{
using System;
sealed class OutboundRulesCollection : RuleCollectionBase {
protected override RuleElement CreateNewElement(string elementTagName) {

Просмотреть файл

@ -27,20 +27,37 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
}
}
public RuleElement AddCopy(RuleElement rule) {
RuleElement element = CreateElement();
public new int IndexOf(RuleElement rule)
{
int index = -1;
CopyInfo(rule, element);
for (int i = 0; i < Count; i++) {
if (this[i].Name.Equals(rule.Name, StringComparison.OrdinalIgnoreCase)) {
index = i;
break;
}
}
return Add(element);
return index;
}
public RuleElement AddCopyAt(int index, RuleElement rule) {
RuleElement element = CreateElement();
public virtual void Move(RuleElement rule, int index)
{
if (index < 0 || index >= this.Count) {
throw new IndexOutOfRangeException();
}
CopyInfo(rule, element);
int currentIndex = IndexOf(rule);
return AddAt(index, element);
if (currentIndex == -1) {
throw new ArgumentException(nameof(rule));
}
//
// Make sure rule comes from this collection instance
RuleElement r = this[currentIndex];
this.RemoveAt(currentIndex);
this.AddCopyAt(index, r);
}
protected virtual void CopyInfo(RuleElement source, RuleElement destination) {
@ -54,11 +71,20 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
ConfigurationHelper.CopyMetadata(source, destination);
}
private RuleElement AddCopyAt(int index, RuleElement rule)
{
RuleElement element = CreateElement();
CopyInfo(rule, element);
return AddAt(index, element);
}
}
static class ConfigurationHelper {
internal static void CopyAttributes(ConfigurationElement source, ConfigurationElement destination) {
public static void CopyAttributes(ConfigurationElement source, ConfigurationElement destination) {
foreach (ConfigurationAttribute attribute in source.Attributes) {
if (!attribute.IsInheritedFromDefaultValue) {
destination[attribute.Name] = attribute.Value;
@ -66,7 +92,7 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
}
}
internal static void CopyMetadata(ConfigurationElement source, ConfigurationElement destination) {
public static void CopyMetadata(ConfigurationElement source, ConfigurationElement destination) {
object o = source.GetMetadata("lockItem");
if (o != null) {
destination.SetMetadata("lockItem", o);

Просмотреть файл

@ -115,7 +115,7 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
InboundRule rule = GlobalRulesHelper.CreateRule(model, section);
GlobalRulesHelper.AddRule(rule, section);
GlobalRulesHelper.AddRule(rule, section, model);
ManagementUnit.Current.Commit();

Просмотреть файл

@ -120,7 +120,7 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
InboundRule rule = InboundRulesHelper.CreateRule(model, site, parentId.Path, ManagementUnit.ResolveConfigScope(model));
// Add it
InboundRulesHelper.AddRule(rule, section);
InboundRulesHelper.AddRule(rule, section, model);
// Save
ManagementUnit.Current.Commit();

Просмотреть файл

@ -118,7 +118,7 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
OutboundRule rule = OutboundRulesHelper.CreateRule(model, section);
OutboundRulesHelper.AddRule(rule, section);
OutboundRulesHelper.AddRule(rule, section, model);
ManagementUnit.Current.Commit();

Просмотреть файл

@ -15,7 +15,7 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
private const string PROVIDERS_ENDPOINT = "entries";
private const string REWRITE_MAPS_SECTION_ENDPOINT = "rewrite-maps";
private const string REWRITE_MAPS_ENDPOINT = "entries";
private const string GLOBAL_RULES_SECTION_ENDPOINT = "global-rules";
private const string GLOBAL_RULES_SECTION_ENDPOINT = "global";
private const string GLOBAL_RULES_ENDPOINT = "rules";
private const string INBOUND_RULES_SECTION_ENDPOINT = "inbound";
private const string INBOUND_RULES_ENDPOINT = "rules";
@ -62,7 +62,7 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
// Global Rules Section
public const string GlobalRulesSectionName = "Microsoft.WebServer.UrlRewrite.GlobalRules";
public static ResDef GlobalRulesSectionResource = new ResDef("global_rules", new Guid("59F0C4C2-B7E9-456C-92DE-CFDB4D313991"), GLOBAL_RULES_SECTION_ENDPOINT);
public static ResDef GlobalRulesSectionResource = new ResDef("global", new Guid("59F0C4C2-B7E9-456C-92DE-CFDB4D313991"), GLOBAL_RULES_SECTION_ENDPOINT);
public static readonly string GLOBAL_RULES_SECTION_PATH = $"{PATH}/{GLOBAL_RULES_SECTION_ENDPOINT}";
// Global Rules

Просмотреть файл

@ -135,6 +135,12 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
obj.id = globalRuleId.Uuid;
}
//
// priority
if (fields.Exists("priority")) {
obj.priority = GetSection(site, path).InboundRules.IndexOf(rule);
}
//
// pattern
if (fields.Exists("pattern")) {
@ -283,10 +289,6 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
throw new ApiArgumentException("pattern");
}
if (string.IsNullOrEmpty(DynamicHelper.Value(model.pattern_syntax))) {
throw new ApiArgumentException("pattern_syntax");
}
if (model.action == null) {
throw new ApiArgumentException("action");
}
@ -295,22 +297,23 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
throw new ApiArgumentException("action", ApiArgumentException.EXPECTED_OBJECT);
}
if (string.IsNullOrEmpty(DynamicHelper.Value(model.action.type))) {
throw new ApiArgumentException("action.type");
}
if (string.IsNullOrEmpty(DynamicHelper.Value(model.action.url))) {
throw new ApiArgumentException("action.url");
}
var rule = (InboundRule)section.InboundRules.CreateElement();
//
// Defaults
rule.PatternSyntax = PatternSyntax.ECMAScript;
rule.Action.Type = ActionType.Rewrite;
SetRule(model, rule, section);
return rule;
}
public static void AddRule(InboundRule rule, InboundRulesSection section)
public static void AddRule(InboundRule rule, InboundRulesSection section, dynamic model)
{
if (rule == null) {
throw new ArgumentNullException(nameof(rule));
@ -328,6 +331,8 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
try {
collection.Add(rule);
UpdatePriority(model, rule, section);
}
catch (FileLoadException e) {
throw new LockedException(section.SectionPath, e);
@ -490,6 +495,22 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
if ((rule.Action.Type == ActionType.Redirect || rule.Action.Type == ActionType.Rewrite) && string.IsNullOrEmpty(rule.Action.Url)) {
throw new ApiArgumentException("action.url");
}
UpdatePriority(model, rule, section);
}
private static void UpdatePriority(dynamic model, InboundRule rule, InboundRulesSection section)
{
if (model == null) {
throw new ApiArgumentException("model");
}
DynamicHelper.If((object)model.priority, 0, int.MaxValue, v => {
v = v >= section.InboundRules.Count ? section.InboundRules.Count - 1 : v;
if (section.InboundRules.IndexOf(rule) != -1) {
section.InboundRules.Move(rule, (int) v);
}
});
}
}
}

Просмотреть файл

@ -119,6 +119,12 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
obj.id = inboundRuleId.Uuid;
}
//
// priority
if (fields.Exists("priority")) {
obj.priority = GetSection(site, path).InboundRules.IndexOf(rule);
}
//
// pattern
if (fields.Exists("pattern")) {
@ -243,10 +249,6 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
throw new ApiArgumentException("pattern");
}
if (string.IsNullOrEmpty(DynamicHelper.Value(model.pattern_syntax))) {
throw new ApiArgumentException("pattern_syntax");
}
if (model.action == null) {
throw new ApiArgumentException("action");
}
@ -255,10 +257,6 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
throw new ApiArgumentException("action", ApiArgumentException.EXPECTED_OBJECT);
}
if (string.IsNullOrEmpty(DynamicHelper.Value(model.action.type))) {
throw new ApiArgumentException("action.type");
}
if (string.IsNullOrEmpty(DynamicHelper.Value(model.action.url))) {
throw new ApiArgumentException("action.url");
}
@ -266,15 +264,19 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
InboundRulesSection section = GetSection(site, path, configPath);
AllowedServerVariablesSection serverVariablesSection = ServerVariablesHelper.GetSection(site, path, configPath);
var rule = (InboundRule)section.InboundRules.CreateElement();
//
// Defaults
rule.PatternSyntax = PatternSyntax.ECMAScript;
rule.Action.Type = ActionType.Rewrite;
SetRule(model, rule, section, serverVariablesSection);
return rule;
}
public static void AddRule(InboundRule rule, InboundRulesSection section)
public static void AddRule(InboundRule rule, InboundRulesSection section, dynamic model)
{
if (rule == null) {
throw new ArgumentNullException(nameof(rule));
@ -292,6 +294,8 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
try {
collection.Add(rule);
UpdatePriority(model, rule, section);
}
catch (FileLoadException e) {
throw new LockedException(section.SectionPath, e);
@ -519,6 +523,22 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
if ((rule.Action.Type == ActionType.Redirect || rule.Action.Type == ActionType.Rewrite) && string.IsNullOrEmpty(rule.Action.Url)) {
throw new ApiArgumentException("action.url");
}
UpdatePriority(model, rule, section);
}
private static void UpdatePriority(dynamic model, InboundRule rule, InboundRulesSection section)
{
if (model == null) {
throw new ApiArgumentException("model");
}
DynamicHelper.If((object)model.priority, 0, int.MaxValue, v => {
v = v >= section.InboundRules.Count ? section.InboundRules.Count - 1 : v;
if (section.InboundRules.IndexOf(rule) != -1) {
section.InboundRules.Move(rule, (int) v);
}
});
}
private static void AddAllowedServerVariable(AllowedServerVariablesSection serverVariablesSection, string name)

Просмотреть файл

@ -274,6 +274,12 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
obj.id = outboundRuleId.Uuid;
}
//
// priority
if (fields.Exists("priority")) {
obj.priority = GetSection(site, path).Rules.IndexOf(rule);
}
// precondition
if (fields.Exists("precondition")) {
var precondition = section.PreConditions.FirstOrDefault(pc => pc.Name.Equals(rule.PreCondition, StringComparison.OrdinalIgnoreCase));
@ -428,10 +434,6 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
throw new ApiArgumentException("pattern");
}
if (string.IsNullOrEmpty(DynamicHelper.Value(model.pattern_syntax))) {
throw new ApiArgumentException("pattern_syntax");
}
if (string.IsNullOrEmpty(DynamicHelper.Value(model.match_type))) {
throw new ApiArgumentException("match_type");
}
@ -441,13 +443,14 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
//
// Default to rewrite rule
rule.Action.Type = OutboundActionType.Rewrite;
rule.PatternSyntax = PatternSyntax.ECMAScript;
SetRule(model, rule, section);
return rule;
}
public static void AddRule(OutboundRule rule, OutboundRulesSection section)
public static void AddRule(OutboundRule rule, OutboundRulesSection section, dynamic model)
{
if (rule == null) {
throw new ArgumentNullException(nameof(rule));
@ -463,6 +466,8 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
try {
section.Rules.Add(rule);
UpdatePriority(model, rule, section);
}
catch (FileLoadException e) {
throw new LockedException(section.SectionPath, e);
@ -844,6 +849,8 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
else {
rule.Match.FilterByTags = FilterByTags.None;
}
UpdatePriority(model, rule, section);
}
private static void SetPreCondition(dynamic model, PreCondition precondition, OutboundRulesSection section)
@ -996,5 +1003,19 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
target &= ~flag;
}
}
private static void UpdatePriority(dynamic model, OutboundRule rule, OutboundRulesSection section)
{
if (model == null) {
throw new ApiArgumentException("model");
}
DynamicHelper.If((object)model.priority, 0, int.MaxValue, v => {
v = v >= section.Rules.Count ? section.Rules.Count - 1 : v;
if (section.Rules.IndexOf(rule) != -1) {
section.Rules.Move(rule, (int) v);
}
});
}
}
}

Просмотреть файл

@ -150,7 +150,7 @@ namespace Microsoft.IIS.Administration.Tests
const string testRuleName = "GlobalTestRule";
string updatedTestRuleName = testRuleName + "2";
JObject globalRulesSection = Utils.FollowLink(client, webserverFeature, "global_rules");
JObject globalRulesSection = Utils.FollowLink(client, webserverFeature, "global");
string globalRulesLink = Utils.GetLink(globalRulesSection, "rules");
IEnumerable<JObject> rules = client.Get(globalRulesLink)["rules"].ToObject<IEnumerable<JObject>>();
@ -688,7 +688,7 @@ namespace Microsoft.IIS.Administration.Tests
{
string[] sections = {
"allowed-server-variables",
"global-rules",
"global",
"inbound",
"outbound",
"providers",
@ -730,6 +730,205 @@ namespace Microsoft.IIS.Administration.Tests
}
}
[Fact]
public async Task InboundRulePriority()
{
using (HttpClient client = ApiHttpClient.Create()) {
await EnsureEnabled(client);
Sites.EnsureNoSite(client, TEST_SITE_NAME);
var site = Sites.CreateSite(client, TEST_SITE_NAME, Utils.GetAvailablePort(), Sites.TEST_SITE_PATH);
Assert.NotNull(site);
try {
JObject siteFeature = Utils.GetFeature(client, REWRITE_URL, site.Value<string>("name"), "/");
Assert.NotNull(siteFeature);
const string name1 = "InboundPriorityTestRule";
const string name2 = name1 + "2";
JObject inboundRulesSection = Utils.FollowLink(client, siteFeature, "inbound");
string inboundRulesLink = Utils.GetLink(inboundRulesSection, "rules");
IEnumerable<JObject> rules = client.Get(inboundRulesLink)["rules"].ToObject<IEnumerable<JObject>>();
foreach (var r in rules) {
if (r.Value<string>("name").Equals(name1, StringComparison.OrdinalIgnoreCase) ||
r.Value<string>("name").Equals(name2, StringComparison.OrdinalIgnoreCase)) {
Assert.True(client.Delete(Utils.Self(r)));
}
}
JObject rule = JObject.FromObject(new {
name = name1,
pattern = "^([^/]+)/([^/]+)/?$",
action = new {
type = "rewrite",
url = "def.aspx?a={R:1}&c={R:2}",
},
url_rewrite = siteFeature
});
JObject rule2 = (JObject)rule.DeepClone();
rule2["name"] = name2;
JObject result = client.Post(inboundRulesLink, rule);
Assert.NotNull(result);
JObject result2 = client.Post(inboundRulesLink, rule2);
Assert.NotNull(result2);
// result 0, result2 1
Assert.Equal(result.Value<int>("priority") + 1, result2.Value<int>("priority"));
result2["priority"] = result.Value<int>("priority");
JObject updatedResult2 = client.Patch(Utils.Self(result2), result2);
// result 0, result2 0, updatedresult2 0
result = Utils.FollowLink(client, result, "self");
// result 1, result2 0, updatedresult2 0
Assert.Equal(result2.Value<int>("priority"), updatedResult2.Value<int>("priority"));
Assert.Equal(result.Value<int>("priority"), updatedResult2.Value<int>("priority") + 1);
Assert.True(client.Delete(Utils.Self(updatedResult2)));
Assert.True(client.Delete(Utils.Self(result)));
}
finally {
Sites.EnsureNoSite(client, site.Value<string>("name"));
}
}
}
[Fact]
public async Task GlobalRulePriority()
{
using (HttpClient client = ApiHttpClient.Create()) {
await EnsureEnabled(client);
JObject feature = Utils.GetFeature(client, REWRITE_URL, null, null);
Assert.NotNull(feature);
const string name1 = "GlobalPriorityTestRule";
const string name2 = name1 + "2";
JObject globalRulesSection = Utils.FollowLink(client, feature, "global");
string rulesLink = Utils.GetLink(globalRulesSection, "rules");
IEnumerable<JObject> rules = client.Get(rulesLink)["rules"].ToObject<IEnumerable<JObject>>();
foreach (var r in rules) {
if (r.Value<string>("name").Equals(name1, StringComparison.OrdinalIgnoreCase) ||
r.Value<string>("name").Equals(name2, StringComparison.OrdinalIgnoreCase)) {
Assert.True(client.Delete(Utils.Self(r)));
}
}
JObject rule = JObject.FromObject(new {
name = name1,
pattern = "^([^/]+)/([^/]+)/?$",
action = new {
type = "rewrite",
url = "def.aspx?a={R:1}&c={R:2}",
},
url_rewrite = feature
});
JObject rule2 = (JObject)rule.DeepClone();
rule2["name"] = name2;
JObject result = client.Post(rulesLink, rule);
Assert.NotNull(result);
JObject result2 = client.Post(rulesLink, rule2);
Assert.NotNull(result2);
// result 0, result2 1
Assert.Equal(result.Value<int>("priority") + 1, result2.Value<int>("priority"));
result2["priority"] = result.Value<int>("priority");
JObject updatedResult2 = client.Patch(Utils.Self(result2), result2);
// result 0, result2 0, updatedresult2 0
result = Utils.FollowLink(client, result, "self");
// result 1, result2 0, updatedresult2 0
Assert.Equal(result2.Value<int>("priority"), updatedResult2.Value<int>("priority"));
Assert.Equal(result.Value<int>("priority"), updatedResult2.Value<int>("priority") + 1);
Assert.True(client.Delete(Utils.Self(updatedResult2)));
Assert.True(client.Delete(Utils.Self(result)));
}
}
[Fact]
public async Task OutboundRulePriority()
{
using (HttpClient client = ApiHttpClient.Create()) {
await EnsureEnabled(client);
Sites.EnsureNoSite(client, TEST_SITE_NAME);
var site = Sites.CreateSite(client, TEST_SITE_NAME, Utils.GetAvailablePort(), Sites.TEST_SITE_PATH);
Assert.NotNull(site);
try {
JObject siteFeature = Utils.GetFeature(client, REWRITE_URL, site.Value<string>("name"), "/");
Assert.NotNull(siteFeature);
const string name1 = "OutboundPriorityTestRule";
const string name2 = name1 + "2";
JObject section = Utils.FollowLink(client, siteFeature, "outbound");
string rulesLink = Utils.GetLink(section, "rules");
IEnumerable<JObject> rules = client.Get(rulesLink)["rules"].ToObject<IEnumerable<JObject>>();
foreach (var r in rules) {
if (r.Value<string>("name").Equals(name1, StringComparison.OrdinalIgnoreCase) ||
r.Value<string>("name").Equals(name2, StringComparison.OrdinalIgnoreCase)) {
Assert.True(client.Delete(Utils.Self(r)));
}
}
JObject rule = JObject.FromObject(new {
name = name1,
pattern = "^([^/]+)/([^/]+)/?$",
match_type = "server_variable",
server_variable = "abcdefg",
url_rewrite = siteFeature
});
JObject rule2 = (JObject)rule.DeepClone();
rule2["name"] = name2;
JObject result = client.Post(rulesLink, rule);
Assert.NotNull(result);
JObject result2 = client.Post(rulesLink, rule2);
Assert.NotNull(result2);
// result 0, result2 1
Assert.Equal(result.Value<int>("priority") + 1, result2.Value<int>("priority"));
result2["priority"] = result.Value<int>("priority");
JObject updatedResult2 = client.Patch(Utils.Self(result2), result2);
// result 0, result2 0, updatedresult2 0
result = Utils.FollowLink(client, result, "self");
// result 1, result2 0, updatedresult2 0
Assert.Equal(result2.Value<int>("priority"), updatedResult2.Value<int>("priority"));
Assert.Equal(result.Value<int>("priority"), updatedResult2.Value<int>("priority") + 1);
Assert.True(client.Delete(Utils.Self(updatedResult2)));
Assert.True(client.Delete(Utils.Self(result)));
}
finally {
Sites.EnsureNoSite(client, site.Value<string>("name"));
}
}
}
private static void AssertInboundRulesEqual(JObject a, JObject b)