创建多个 (15+) HTTP 响应过滤器,继承与组合/注入

发布于 2024-10-07 18:10:10 字数 4516 浏览 2 评论 0原文

首先是关于我想要实现的目标的一些背景故事。

我正在创建一个自定义 HTTP 模块,其目的是拦截发送到多个 (15+) 不同 ArcGIS REST Web 服务的消息。拦截的请求和/或响应将被剥离基于当前用户的任何受限信息。

例如,返回多个层的调用可能会删除某些层。

未修改的响应:

"layers" : [
    {
      "id" : 0, 
      "name" : "Facilities", 
      "parentLayerId" : -1, 
      "defaultVisibility" : true, 
      "subLayerIds" : [1, 2, 3]
    }, 
    {
      "id" : 1, 
      "name" : "Hazardous Sites", 
      "parentLayerId" : 0, 
      "defaultVisibility" : true, 
      "subLayerIds" : null
    }, 
]

修改的响应:

"layers" : [
    {
      "id" : 0, 
      "name" : "Facilities", 
      "parentLayerId" : -1, 
      "defaultVisibility" : true, 
      "subLayerIds" : [1, 2, 3]
    }
]

有许多可用的服务,所有服务都通过 URL 进行唯一标识。每个服务返回的信息非常不同,因此需要进行不同的过滤。此外,每个服务可能会以各种格式(HTML、JSON 等)返回数据。

因此,我需要创建大量不同的过滤器来应用于 HttpRequest.Filters 和/或 HttpResponse.Filters。

示例:

// Request for layers and the format is JSON
IPolicy policy = GetPolicy(userContext);
Filter filter = new LayerJsonResponseFilter(Response.Filter, policy);
Response.Filter = filter;

请求和响应过滤器是通过继承 Stream(或继承自 Stream 的其他类,例如 MemoryStream)来实现的。我希望能够轻松创建新的过滤器,而无需为每个过滤器重新实现 Stream。

这里描述了一个潜在的解决方案: http://www.west-wind.com /weblog/posts/72596.aspx

但是,我想简化解决方案,而又不失去指定许多不同转换而不重新实现流的灵活性。我认为可以通过以下方式实现:

  1. 继承MemoryStream,以减少方法的重新实现。
  2. 始终对完整内容进行操作,而不是分块内容。
  3. 用抽象方法(例如 Filter())替换事件

我考虑了两种可能的解决方案。

解决方案 1:创建继承自 ResponseFilter 的多个过滤器

在此方案中,每个过滤器都包含用于执行过滤的逻辑。将创建 15 个以上的过滤器,所有过滤器都继承自公共 ResponseFilter 抽象基类,如下所示:

// All filters will inherit from ResponseFilter
public abstract class ResponseFilter : MemoryStream
{
    public ResponseFilter(Stream stream, Policy policy) { }

    // Must be overridden in a derived class with specific Filter logic.
    public abstract string Filter(string content);

    // Overridden to cache content.    
    public override void Write(byte[] buffer, int offset, int count) { }

    // Overridden to perform the filter/transformation before the content is written.
    public override void Flush()
    {
         // Get stream content as a string

         string content = Filter(content);

         // Write new content to stream
    }
}

这将按以下方式使用。

// Example
var policy = GetPolicy();
var filter = new MapServiceJsonResponseFilter(response.Filter, policy);
response.Filter = filter;

此选项的优点是可以将类的数量保持在最低限度。然而,如果有必要的话,在应用程序中的其他任何地方重用任何过滤器逻辑就会变得困难。此外,对过滤器进行单元测试需要模拟 Stream,这是另一个缺点。

解决方案 2:创建多个过滤器,注入公共 ResponseFilter

在此方案中,将创建单个响应过滤器。实际的过滤器逻辑或算法被注入到过滤器中。所有过滤器都继承自抽象基类FilterBase。

// Represents an HttpResponse Filter. Renamed to avoid confusion with
// the filter algorithm.
public class ResponseFilterStream : MemoryStream
{
    public ResponseFilterStream(Stream stream, FilterBase filter) { }

    // Overridden to cache content.    
    public override void Write(byte[] buffer, int offset, int count) { }

    // Overridden to perform the filter/transformation before the content is written.
    public override void Flush()
    {
         // Get stream content as a string

         string content = _filter.Filter(content);

         // Write new content to stream
    }
}

// All filter algorithms inherit from FilterBase and must implement 
// the filter method.
public abstract class FilterBase
{
    protected TransformBase(Policy policy) { }

    // Overridden to perform the filter/transformation.    
    public abstract string Filter(string content);
}

这将按以下方式使用。

// Example
var policy = GetPolicy();
var filter = new MapServiceJsonResponseFilter(policy);
ResponseFilter responseFilter = new ResponseFilter(response.Filter, filter);
response.Filter = filter;

该解决方案的优点是过滤逻辑完全独立于任何实现流的类。如有必要,可以更轻松地重用逻辑。单元测试稍微简单一些,而且我不需要模拟流。

然而,有更多的类(恰好是 1 个),并且用法也稍微复杂一些,但也不是很复杂。

注意:我可能想重命名 FilterBase 以避免与 ResponseFilter 混淆。也许是 TransformBase。


我有几个目标希望通过任一解决方案来实现。

  1. 该解决方案必须具有高度可测试性。单元测试将用于检查过滤器的正确性。测试必须尽可能简单。
  2. 该解决方案必须轻松支持创建多个过滤器(15+)。
  3. 该解决方案应该是可读的(即易于维护)。

我认为解决方案 2 是针对该给定场景的最佳解决方案。我可以完全独立于 Stream 来测试过滤逻辑,并以最小的额外复杂性进行测试。任一解决方案都支持 #2 和 #3,因此测试具有优势。

可能还有哪些其他考虑因素?有更好的选择吗?

First a little back story on what I am trying to accomplish.

I am in the process of creating a custom HTTP Module whose purposes is to intercept messages to multiple (15+) different ArcGIS REST web services. The intercepted requests and/or responses will be stripped of any restricted information based on the current user.

For instance, a call that returns multiple layers might have certain layers stripped out.

Unmodified Response:

"layers" : [
    {
      "id" : 0, 
      "name" : "Facilities", 
      "parentLayerId" : -1, 
      "defaultVisibility" : true, 
      "subLayerIds" : [1, 2, 3]
    }, 
    {
      "id" : 1, 
      "name" : "Hazardous Sites", 
      "parentLayerId" : 0, 
      "defaultVisibility" : true, 
      "subLayerIds" : null
    }, 
]

Modified Response:

"layers" : [
    {
      "id" : 0, 
      "name" : "Facilities", 
      "parentLayerId" : -1, 
      "defaultVisibility" : true, 
      "subLayerIds" : [1, 2, 3]
    }
]

There are numerous services available, all uniquely identified via a URL. Each service returns very different information and so needs to be filtered different. Additionally, each service may return the data in a variety of formats (HTML, JSON, etc).

As such, I will need to create a multitude of different filters to apply to HttpRequest.Filters and/or HttpResponse.Filters.

Example:

// Request for layers and the format is JSON
IPolicy policy = GetPolicy(userContext);
Filter filter = new LayerJsonResponseFilter(Response.Filter, policy);
Response.Filter = filter;

Request and response filters are implemented by inheriting from Stream (or another class that inherits from Stream such as MemoryStream). I want to be able to easily create new filters without reimplementing Stream for each filter.

A potential solution is described in here: http://www.west-wind.com/weblog/posts/72596.aspx

However, I want to simplify the solution without losing the flexibility of specifying many different transformations without reimplementing the stream. I think that I can accomplish this by:

  1. Inherit from MemoryStream so as to reduce the reimplementation of methods.
  2. Always operate on full content, rather than chunked content.
  3. Replace the events with an abstract method (e.g., Filter())

I have considered two potential solutions.

Solution 1: Create Multiple Filters Inheriting from ResponseFilter

In this scenario each filter contains the logic for performing the filtration. There would be 15+ filters created all inheriting from a common ResponseFilter abstract base class like so:

// All filters will inherit from ResponseFilter
public abstract class ResponseFilter : MemoryStream
{
    public ResponseFilter(Stream stream, Policy policy) { }

    // Must be overridden in a derived class with specific Filter logic.
    public abstract string Filter(string content);

    // Overridden to cache content.    
    public override void Write(byte[] buffer, int offset, int count) { }

    // Overridden to perform the filter/transformation before the content is written.
    public override void Flush()
    {
         // Get stream content as a string

         string content = Filter(content);

         // Write new content to stream
    }
}

This would be used in the following way.

// Example
var policy = GetPolicy();
var filter = new MapServiceJsonResponseFilter(response.Filter, policy);
response.Filter = filter;

The advantage to this option is that the number of classes is kept to a minimum. However, it becomes difficult to reuse any filter logic anywhere else in the application should it become necessary. Additionally, unit testing the filters would require mocking the Stream, another disadvantage.

Solution 2: Create Multiple Filters, Inject into a Common ResponseFilter

In this scenario, a single response filter is created. The actual filter logic or algorithm is injected into the filter. All filters inherit from an abstract base class FilterBase.

// Represents an HttpResponse Filter. Renamed to avoid confusion with
// the filter algorithm.
public class ResponseFilterStream : MemoryStream
{
    public ResponseFilterStream(Stream stream, FilterBase filter) { }

    // Overridden to cache content.    
    public override void Write(byte[] buffer, int offset, int count) { }

    // Overridden to perform the filter/transformation before the content is written.
    public override void Flush()
    {
         // Get stream content as a string

         string content = _filter.Filter(content);

         // Write new content to stream
    }
}

// All filter algorithms inherit from FilterBase and must implement 
// the filter method.
public abstract class FilterBase
{
    protected TransformBase(Policy policy) { }

    // Overridden to perform the filter/transformation.    
    public abstract string Filter(string content);
}

This would be used in the following way.

// Example
var policy = GetPolicy();
var filter = new MapServiceJsonResponseFilter(policy);
ResponseFilter responseFilter = new ResponseFilter(response.Filter, filter);
response.Filter = filter;

The advantage to this solution is that the filtration logic is completely independent of any classes that implement stream. The logic can be more easily reused if necessary. Unit testing is a little simpler as well as I do not need to mock the stream.

However, there are more classes (exactly 1) and the usage is a little more complex, though not terribly so.

Note: I'll probably want to rename FilterBase as to avoid confusion with ResponseFilter. Perhaps TransformBase.


I have several goals that I want to meet with either solution.

  1. The solution must be highly testable. Unit testing will be used to check for correctness of the filters. It is imperative that testing is as simple as possible.
  2. The solution must easily support the creation of multiple filters (15+).
  3. The solution should be readable (i.e., easy to maintain).

I think that solution 2 is the best solution for this given scenario. I can test the filtration logic completely independently of Stream with minimal additional complexity. Either solution will support #2 and #3, so testing gets the edge.

What other considerations might there be? Are there better alternatives?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

沫离伤花 2024-10-14 18:10:10

显然方案2更可取。然而,问题的主要症结似乎在于过滤器本身的构造。希望 Filter 实现中有很多可重用的组合。可以用复合部件“配置”新的过滤器吗?

Solution 2 is obviously preferable. However, it seems that the major crux of the problem lies in the construction of the Filters themselves. Hopefully there is a lot of reusable composition within the Filter implementations. Can a new filter be "configured" from composite parts?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文