{"id":109028,"date":"2023-11-17T07:00:00","date_gmt":"2023-11-17T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109028"},"modified":"2023-11-19T10:50:27","modified_gmt":"2023-11-19T18:50:27","slug":"20231117-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231117-00\/?p=109028","title":{"rendered":"The theory behind the IHttpFilter interface"},"content":{"rendered":"<p>The Windows Runtime has an interface called <code>IHttpFilter<\/code> that lets you customize how HTTP requests are processed. The system has a default implementation called <code>Http\u00adBase\u00adProtocol\u00adFilter<\/code>, and if it has the features you need, then you&#8217;re all set:<\/p>\n<pre>auto filter = HttpBaseProtocolFilter();\r\nfilter.AllowAutoRedirect(false); \/\/ disable auto-redirect\r\nauto client = HttpClient(filter);\r\n\u27e6 Use the client \u27e7\r\n<\/pre>\n<p>But maybe you want to do something beyond that. For example, maybe you want a filter that injects an extra header into each request. You can implement these extra features by providing your own implementation of <code>IHttpFilter<\/code>, using the default implementation to do the heavy lifting.<\/p>\n<pre>\/\/ IDL\r\nruntimeclass ExtraHeadersHttpFilter : Windows.Web.Http.Filters.IHttpFilter\r\n{\r\n    ExtraHeadersHttpFilter();\r\n    void AddHeader(string name, string value);\r\n}\r\n\r\n\/\/ Implementation\r\n\r\nstruct ExtraHeadersHttpFilter : ExtraHeadersHttpFilterT&lt;ExtraHeadersHttpFilter&gt;\r\n{\r\n    using namespace Http = winrt::Windows::Web::Http;\r\nprivate:\r\n    \/\/ Data members\r\n    std::mutex m_mutex;\r\n    std::unordered_map&lt;winrt::hstring, winrt::hstring&gt; m_extraHeaders;\r\n    Http::Filters::HttpBaseProtocolFilter m_base = Http:Filters::HttpBaseProtocolFilter();\r\n\r\npublic:\r\n    ExtraHeadersHttpFilter() = default;\r\n\r\n    \/\/ Bonus methods for public consumption\r\n    void AddHeader(winrt::hstring const&amp; name,\r\n                   winrt::hstring const&amp; value)\r\n    {\r\n        auto lock = std::lock_guard(m_mutex);\r\n        m_extraHeaders.insert_or_assign(name, value);\r\n    }\r\n\r\n    \/\/ IHttpFilter methods\r\n    winrt::Windows::Foundation::IAsyncOperationWithProgress&lt;Http::HttpResponseMessage,\r\n        Http::HttpProgress&gt; SendRequestAsync(Http::HttpRequestMessage request)\r\n    {\r\n        \/\/ Add our bonus headers to the request\r\n        {\r\n            auto lock = std::lock_guard(m_mutex);\r\n            auto headers = request.Headers();\r\n            for (auto [name, value] : m_extraHeaders) {\r\n                headers.Insert(name, value);\r\n            }\r\n        }\r\n\r\n        \/\/ And then use default handling for the rest.\r\n        return m_base.SendRequestAsync(request);\r\n    }\r\n};\r\n\r\n\/\/ Consumer\r\nauto filter = ExtraHeadersHttpFilter();\r\nfilter.AddHeader(L\"X-Contoso\", L\"Awesome\");\r\nauto client = HttpClient(filter);\r\n\u27e6 Use the client \u27e7\r\n<\/pre>\n<p>One thing that people like to do is stack filters on top of each other. Maybe you want to use the <code>Extra\u00adHeaders\u00adHttp\u00adFilter<\/code> in conjunction with a <code>Custom\u00adRetry\u00adHttp\u00adFilter<\/code>. Right now, the <code>Extra\u00adHeaders\u00adHttp\u00adFilter<\/code> hands all of its requests to the default <code>Http\u00adBase\u00adProtocol\u00adFilter<\/code>, so there&#8217;s no way to combine it with a <code>Custom\u00adRetry\u00adHttp\u00adFilter<\/code>.<\/p>\n<p>The way to fix this is to give the <code>Extra\u00adHeaders\u00adHttp\u00adFilter<\/code> a constructor that takes another filter. Requests then pass through that custom filter instead of going to the default filter.<\/p>\n<pre>\/\/ IDL\r\nruntimeclass ExtraHeadersHttpFilter : Windows.Web.Http.Filters.IHttpFilter\r\n{\r\n    ExtraHeadersHttpFilter();\r\n    <span style=\"border: solid 1px currentcolor;\">ExtraHeadersHttpFilter(Windows.Web.Http.Filters.IHttpFilter baseFilter);<\/span>\r\n    void AddHeader(string name, string value);\r\n}\r\n\r\n\/\/ Implementation\r\n\r\nstruct ExtraHeadersHttpFilter : ExtraHeadersHttpFilterT&lt;ExtraHeadersHttpFilter&gt;\r\n{\r\n    using namespace Http = winrt::Windows::Web::Http;\r\nprivate:\r\n    \/\/ Data members\r\n    std::mutex m_mutex;\r\n    std::unordered_map&lt;winrt::hstring, winrt::hstring&gt; m_extraHeaders;\r\n    Http:Filters::<span style=\"border: solid 1px currentcolor;\">IHttpFilter<\/span> m_base = Http:Filters::HttpBaseProtocolFilter();\r\n\r\npublic:\r\n    ExtraHeadersHttpFilter() = default;\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">ExtraHeadersHttpFilter(Http::Filters::IHttpFilter const&amp; baseFilter) :<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">    m_base(baseFilter) {}                                             <\/span>\r\n\r\n    \/\/ Bonus methods for public consumption\r\n    void AddHeader(winrt::hstring const&amp; name,\r\n                   winrt::hstring const&amp; value)\r\n    {\r\n        auto lock = std::lock_guard(m_mutex);\r\n        m_extraHeaders.insert_or_assign(name, value);\r\n    }\r\n\r\n    \/\/ IHttpFilter methods\r\n    winrt::Windows::Foundation::IAsyncOperationWithProgress&lt;Http::HttpResponseMessage,\r\n        Http::HttpProgress&gt; SendRequestAsync(Http::HttpRequestMessage request)\r\n    {\r\n        \/\/ Add our bonus headers to the request\r\n        {\r\n            auto lock = std::lock_guard(m_mutex);\r\n            auto headers = request.Headers();\r\n            for (auto [name, value] : m_extraHeaders) {\r\n                headers.Insert(name, value);\r\n            }\r\n        }\r\n\r\n        <span style=\"border: solid 1px currentcolor;\">\/\/ And then pass the request to the base filter.<\/span>\r\n        return m_base.SendRequestAsync(request);\r\n    }\r\n};\r\n\r\n\/\/ Consumer\r\nauto retryFilter = CustomRetryHttpFilter(3);\r\nauto headerFilter = ExtraHeadersHttpFilter(retryFilter);\r\nheaderFilter.AddHeader(L\"X-Contoso\", L\"Awesome\");\r\nauto client = HttpClient(headerFilter);\r\n\u27e6 Use the client \u27e7\r\n<\/pre>\n<p>The idea behind HTTP filters is that each request passes through the filters before hitting the wire, and then the response passes through the filters on the way back.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>App<\/td>\n<td>&nbsp;<\/td>\n<td>Filter1<\/td>\n<td>&nbsp;<\/td>\n<td>Filter2<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>FilterN<\/td>\n<td>&nbsp;<\/td>\n<td>Network<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px currentColor; border-bottom: none; text-align: center;\">Request<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px currentColor; border-bottom: none; text-align: center;\">\u2022<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px currentColor; border-bottom: none; text-align: center;\">\u2022<\/td>\n<td>\u2192<\/td>\n<td>\u2026<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px currentColor; border-bottom: none; text-align: center;\">\u2022<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px currentColor; border-bottom: none; text-align: center;\">\u21b4<\/td>\n<\/tr>\n<tr>\n<td style=\"border: 1px currentColor; border-style: none solid;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentColor; border-style: none solid;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentColor; border-style: none solid; text-align: center;\">\u2191<\/td>\n<td>&nbsp;<\/td>\n<td>\u2026<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentColor; border-style: none solid;\">\u00a0<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px currentColor; border-style: none solid; text-align: center;\">\u2022<\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px currentColor; border-top: none; text-align: center;\">\u2022<\/td>\n<td>\u2190<\/td>\n<td style=\"border: solid 1px currentColor; border-top: none; text-align: center;\">\u2022<\/td>\n<td>\u2190<\/td>\n<td style=\"border: solid 1px currentColor; border-top: none; text-align: center;\">\u2022<\/td>\n<td>\u2190<\/td>\n<td>\u2026<\/td>\n<td>\u2190<\/td>\n<td style=\"border: solid 1px currentColor; border-top: none; text-align: center;\">\u2022<\/td>\n<td>\u2190<\/td>\n<td style=\"border: solid 1px currentColor; border-top: none; text-align: center;\">Response<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In the above diagram, Filter2 is a Retry filter, so when it gets a response, it might decide to generate a new request and submit that one down the pipeline.<\/p>\n<p>Basically, each filter can treat the remainder of the pipeline as a black box which conceptually submits the request and produces a result. Your filter doesn&#8217;t even have to submit the request down the pipeline; maybe it sees something wrong with the request and chooses to generate an error response without actually hitting the wire. Or it might submit the request to two downstream pipelines for some reason. Or you may have a custom filter for testing purposes that replays prerecorded Web responses without every submitting them to the network at all. Each filter can decide whatever it likes.<\/p>\n<p><b>Bonus chatter<\/b>: For expository purposes, I didn&#8217;t show how to modify the result. You can find an example of that in the HttpClient sample&#8217;s <a href=\"https:\/\/github.com\/microsoft\/Windows-universal-samples\/blob\/ad9a0c4def222aaf044e51f8ee0939911cb58471\/Samples\/HttpClient\/cppwinrt\/PlugInFilter.cpp#L32\"> PluginFilter<\/a> filter.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Layering features on top of each other.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-109028","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Layering features on top of each other.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109028","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=109028"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109028\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=109028"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109028"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109028"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}