{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"cb45e6dc-ba61-576d-adc2-ba609b0e722a","frontmatter":{"category":"Engineering","title":"Go 1.23: the new Pattern property in http.Request","date":"2024-11-26","summary":"A short review of the new Pattern property in net/http.Request","thumbnail":null,"authorName":"Andrei Gusev","authorDescription":"Senior Software Engineer at AUTO1 Group","authorAvatar":{"relativePath":"pages/go-123-the-new-pattern-property-in-http-request/avatar_Andrei.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAUBBgT/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAQL/2gAMAwEAAhADEAAAAaeTsi2mDmfWV0wzf//EAB0QAAICAQUAAAAAAAAAAAAAAAECAAMEEBESEyH/2gAIAQEAAQUCtYiUvyE71ZkuQNuzaYvuQTP/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAcEAACAgIDAAAAAAAAAAAAAAABEQACEBIhMTL/2gAIAQEABj8C4KnprGnRhqdnHWpWKPH/xAAdEAACAgMAAwAAAAAAAAAAAAABEQAhEEFRMXGB/9oACAEBAAE/IRoFjyQHGILdksEYAYLXeSuANz4mvAhI736hHP/aAAwDAQACAAMAAAAQNBc+/8QAFhEAAwAAAAAAAAAAAAAAAAAAABAR/9oACAEDAQE/ECv/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAcEAEBAAMBAAMAAAAAAAAAAAABEQAhUTEQYaH/2gAIAQEAAT8Q0H5agDhjJzCu2+nxvKetJ9/fd9x0k6GIzjdTPf69KX9xcAAKlBR2wEDDmf/Z","width":50,"height":50,"src":"/static/479e481648d69b84a342bd2575a225d4/d2d31/avatar_Andrei.jpg","srcSet":"/static/479e481648d69b84a342bd2575a225d4/d2d31/avatar_Andrei.jpg 1x,\n/static/479e481648d69b84a342bd2575a225d4/0b804/avatar_Andrei.jpg 1.5x,\n/static/479e481648d69b84a342bd2575a225d4/753c3/avatar_Andrei.jpg 2x,\n/static/479e481648d69b84a342bd2575a225d4/31ca8/avatar_Andrei.jpg 3x"}}},"headerImage":null},"html":"<h1>Go 1.23: the new Pattern property in http.Request</h1>\n<p>In Go 1.23, a new <code class=\"language-text\">Pattern</code> property in <code class=\"language-text\">net/http.Request</code> was added.\nIt contains the route pattern used to handle the request. This improvement may have gone unnoticed by many, but it can\nsimplify request processing and improve performance in cases where we need to obtain the matched routing pattern\nfor the current request. Let’s see how we can use this feature.</p>\n<p>At AUTO1, we have more than 200 microservices, some of which are written in Go and include an HTTP server.\nIt is critically important for us to collect metrics from these services in order to quickly respond to incidents,\nmake data-driven decisions, and optimize system performance.\nFor example, we collect <code class=\"language-text\">http_requests_total</code> metric:</p>\n<div class=\"gatsby-highlight\" data-language=\"go\"><pre class=\"language-go\"><code class=\"language-go\">requestsTotal <span class=\"token operator\">=</span> promauto<span class=\"token punctuation\">.</span><span class=\"token function\">NewCounterVec</span><span class=\"token punctuation\">(</span>prometheus<span class=\"token punctuation\">.</span>CounterOpts<span class=\"token punctuation\">{</span>\n\t\tName<span class=\"token punctuation\">:</span> <span class=\"token string\">\"http_requests_total\"</span><span class=\"token punctuation\">,</span>\n\t\tHelp<span class=\"token punctuation\">:</span> <span class=\"token string\">\"total request count\"</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token builtin\">string</span><span class=\"token punctuation\">{</span><span class=\"token string\">\"path\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"method\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"code\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>Obviously, for the <code class=\"language-text\">path</code> label, we should store the path template, not the specific value, for example, <code class=\"language-text\">/test/{id}</code> instead of <code class=\"language-text\">/test/123</code>.\nOtherwise, we risk a cardinality explosion, which can significantly increase the amount of stored data. You can read how to choose labels properly in <a href=\"https://prometheus.io/docs/practices/naming/#labels\">\"Prometheus best practices\"</a>.</p>\n<h2>Before v1.23</h2>\n<p>Previously, to extract the route pattern, we had to pass the multiplexer to a middleware function:</p>\n<div class=\"gatsby-highlight\" data-language=\"go\"><pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">func</span> <span class=\"token function\">metricsMiddleware</span><span class=\"token punctuation\">(</span>next http<span class=\"token punctuation\">.</span>Handler<span class=\"token punctuation\">,</span> mux <span class=\"token operator\">*</span>http<span class=\"token punctuation\">.</span>ServeMux<span class=\"token punctuation\">)</span> http<span class=\"token punctuation\">.</span>Handler <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">HandlerFunc</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">func</span><span class=\"token punctuation\">(</span>w http<span class=\"token punctuation\">.</span>ResponseWriter<span class=\"token punctuation\">,</span> r <span class=\"token operator\">*</span>http<span class=\"token punctuation\">.</span>Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token boolean\">_</span><span class=\"token punctuation\">,</span> pattern <span class=\"token operator\">:=</span> mux<span class=\"token punctuation\">.</span><span class=\"token function\">Handler</span><span class=\"token punctuation\">(</span>r<span class=\"token punctuation\">)</span>\n\t\t\n\t\t<span class=\"token operator\">...</span>\n\t\trequestsTotal<span class=\"token punctuation\">.</span><span class=\"token function\">WithLabelValues</span><span class=\"token punctuation\">(</span>pattern<span class=\"token punctuation\">,</span> method<span class=\"token punctuation\">,</span> statusCode<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Inc</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>In this approach, we matched the request to the route a second time, and that negatively affected performance.</p>\n<h2>Since 1.23</h2>\n<p>Starting with Go 1.23, we can get the route pattern directly from the request, without passing the multiplexer to the middleware:</p>\n<div class=\"gatsby-highlight\" data-language=\"go\"><pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">func</span> <span class=\"token function\">middleware</span><span class=\"token punctuation\">(</span>f http<span class=\"token punctuation\">.</span>Handler<span class=\"token punctuation\">)</span> http<span class=\"token punctuation\">.</span>Handler <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">HandlerFunc</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">func</span><span class=\"token punctuation\">(</span>w http<span class=\"token punctuation\">.</span>ResponseWriter<span class=\"token punctuation\">,</span> r <span class=\"token operator\">*</span>http<span class=\"token punctuation\">.</span>Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\tpattern <span class=\"token operator\">:=</span> r<span class=\"token punctuation\">.</span>Pattern\n\t\t\n\t\t<span class=\"token operator\">...</span>\n\t\trequestsTotal<span class=\"token punctuation\">.</span><span class=\"token function\">WithLabelValues</span><span class=\"token punctuation\">(</span>pattern<span class=\"token punctuation\">,</span> method<span class=\"token punctuation\">,</span> statusCode<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Inc</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>This significantly simplifies the middleware, making the code cleaner and more readable. We are no longer dependent on <code class=\"language-text\">ServeMux</code> and no longer need to re-match the request.</p>\n<h2>Limitations</h2>\n<p>It is important to remember that <code class=\"language-text\">Request.Pattern</code> is only available after the request has been matched to a route. For example:</p>\n<div class=\"gatsby-highlight\" data-language=\"go\"><pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">func</span> <span class=\"token function\">main</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\tmux <span class=\"token operator\">:=</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">NewServeMux</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\tmux<span class=\"token punctuation\">.</span><span class=\"token function\">Handle</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/test/{id}\"</span><span class=\"token punctuation\">,</span> <span class=\"token function\">routeMiddleware</span><span class=\"token punctuation\">(</span><span class=\"token function\">handler</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n\tsrv <span class=\"token operator\">:=</span> http<span class=\"token punctuation\">.</span>Server<span class=\"token punctuation\">{</span>\n\t\tAddr<span class=\"token punctuation\">:</span>    <span class=\"token string\">\":8080\"</span><span class=\"token punctuation\">,</span>\n\t\tHandler<span class=\"token punctuation\">:</span> <span class=\"token function\">globalMiddleware</span><span class=\"token punctuation\">(</span>mux<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">if</span> err <span class=\"token operator\">:=</span> srv<span class=\"token punctuation\">.</span><span class=\"token function\">ListenAndServe</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token operator\">!</span>errors<span class=\"token punctuation\">.</span><span class=\"token function\">Is</span><span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">,</span> http<span class=\"token punctuation\">.</span>ErrServerClosed<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\tlog<span class=\"token punctuation\">.</span><span class=\"token function\">Fatalf</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"server error: %s\"</span><span class=\"token punctuation\">,</span> err<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">func</span> <span class=\"token function\">routeMiddleware</span><span class=\"token punctuation\">(</span>h http<span class=\"token punctuation\">.</span>Handler<span class=\"token punctuation\">)</span> http<span class=\"token punctuation\">.</span>Handler <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">HandlerFunc</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">func</span><span class=\"token punctuation\">(</span>w http<span class=\"token punctuation\">.</span>ResponseWriter<span class=\"token punctuation\">,</span> r <span class=\"token operator\">*</span>http<span class=\"token punctuation\">.</span>Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\tlog<span class=\"token punctuation\">.</span><span class=\"token function\">Printf</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"ROUTE pattern: %s\"</span><span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>Pattern<span class=\"token punctuation\">)</span>\n\t\th<span class=\"token punctuation\">.</span><span class=\"token function\">ServeHTTP</span><span class=\"token punctuation\">(</span>w<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">func</span> <span class=\"token function\">globalMiddleware</span><span class=\"token punctuation\">(</span>h http<span class=\"token punctuation\">.</span>Handler<span class=\"token punctuation\">)</span> http<span class=\"token punctuation\">.</span>Handler <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">HandlerFunc</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">func</span><span class=\"token punctuation\">(</span>w http<span class=\"token punctuation\">.</span>ResponseWriter<span class=\"token punctuation\">,</span> r <span class=\"token operator\">*</span>http<span class=\"token punctuation\">.</span>Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\tlog<span class=\"token punctuation\">.</span><span class=\"token function\">Printf</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"GLOBAL pattern: %s\"</span><span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>Pattern<span class=\"token punctuation\">)</span>\n\t\th<span class=\"token punctuation\">.</span><span class=\"token function\">ServeHTTP</span><span class=\"token punctuation\">(</span>w<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">)</span>\n\t\tlog<span class=\"token punctuation\">.</span><span class=\"token function\">Printf</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"GLOBAL pattern after: %s\"</span><span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>Pattern<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>If we send a request <code class=\"language-text\">GET http://localhost:8080/test/123</code>, in logs we will see:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">2024/11/10 13:20:37 GLOBAL pattern: \n2024/11/10 13:20:37 ROUTE pattern: /test/{id}\n2024/11/10 13:20:37 GLOBAL pattern after: /test/{id}</code></pre></div>\n<p>This is the expected behavior. The multiplexer implements the <code class=\"language-text\">http.Handler</code> interface, and inside the <code class=\"language-text\">ServeHTTP</code> method, it searches for the handler that matches the request. Starting from Go 1.23, it fills the <code class=\"language-text\">Pattern</code> property of the request with the matching route pattern:</p>\n<div class=\"gatsby-highlight\" data-language=\"go\"><pre class=\"language-go\"><code class=\"language-go\"><span class=\"token comment\">// net/http/server.go</span>\n<span class=\"token keyword\">func</span> <span class=\"token punctuation\">(</span>mux <span class=\"token operator\">*</span>ServeMux<span class=\"token punctuation\">)</span> <span class=\"token function\">ServeHTTP</span><span class=\"token punctuation\">(</span>w ResponseWriter<span class=\"token punctuation\">,</span> r <span class=\"token operator\">*</span>Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token operator\">...</span>\n\t<span class=\"token keyword\">var</span> h Handler\n\t<span class=\"token keyword\">if</span> use121 <span class=\"token punctuation\">{</span>\n\t\th<span class=\"token punctuation\">,</span> <span class=\"token boolean\">_</span> <span class=\"token operator\">=</span> mux<span class=\"token punctuation\">.</span>mux121<span class=\"token punctuation\">.</span><span class=\"token function\">findHandler</span><span class=\"token punctuation\">(</span>r<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n\t\th<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>Pattern<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>pat<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>matches <span class=\"token operator\">=</span> mux<span class=\"token punctuation\">.</span><span class=\"token function\">findHandler</span><span class=\"token punctuation\">(</span>r<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span>\n\th<span class=\"token punctuation\">.</span><span class=\"token function\">ServeHTTP</span><span class=\"token punctuation\">(</span>w<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Thus, the <code class=\"language-text\">r.Pattern</code> is filled only after the <code class=\"language-text\">ServeHTTP</code> method of the multiplexer has been called, and the logs confirm this behavior.</p>\n<h2>Conclusion</h2>\n<p>In the last two Go releases, the <code class=\"language-text\">net/http</code> package has received significant improvements that reduced the gap with third-party packages, like <code class=\"language-text\">gorilla/mux</code>. Now, <code class=\"language-text\">net/http.ServeMux</code> supports HTTP methods and wildcards in routing patterns, bringing its functionality closer to what third-party packages previously provided.</p>\n<p>Additionally, the introduction of the <code class=\"language-text\">net/http.Request.Pattern</code> property in Go 1.23 has simplified middleware development by allowing us to access the route pattern without needing to re-match the request, improving performance and reducing code complexity.</p>\n<p>With each of these improvements, there are fewer reasons to choose third-party routers. While the choice between the standard package and third-party routers used to be more obvious, today I would recommend starting with the standard <code class=\"language-text\">net/http</code> package.</p>","fields":{"slug":"/go-123-the-new-pattern-property-in-http-request/","tags":["auto1","go","network_programming"]}}}]}},"pageContext":{"slug":"/tags/go","tag":"go","categories":["Architecture","Coding","DevOps","Engineering","ProjectManagement","QA","Social","TechRadar"]}}