
{"id":5240,"date":"2025-04-10T10:33:10","date_gmt":"2025-04-10T14:33:10","guid":{"rendered":"https:\/\/ikriv.com\/blog\/?p=5240"},"modified":"2025-04-10T10:36:15","modified_gmt":"2025-04-10T14:36:15","slug":"cors-proxies-and-the-trailing-slash-issue","status":"publish","type":"post","link":"https:\/\/ikriv.com\/blog\/?p=5240","title":{"rendered":"CORS, proxies, and the trailing slash issue"},"content":{"rendered":"<p><b>TL;DR<\/b> On the client side, use trailing slash when requesting the &#8220;default&#8221; resource from a Flask app.<\/p>\n<p>Server:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\n# Python flask server at \/var\/www\/api\/myapp\r\napp = Flask(__name__)\r\n\r\n@app.route(&#039;\/&#039;)\r\ndef get_root_resource():\r\n    ...\r\n    return xyz\r\n<\/pre>\n<p>Client:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n\/\/ Javascript client\r\nconst response = await fetch(&quot;\/api\/myapp\/); \/\/ note the trailing slash\r\n<\/pre>\n<h2>Why it matters<\/h2>\n<h3>In Production<\/h3>\n<p>If you make a request without the trailing slash, e.g. <code>https:\/\/myserver.com\/api\/myapp<\/code>, the server would redirect it <code>308 PERMANENT REDIRECT<\/code> to <code>https:\/\/myserver.com\/api\/myapp\/<\/code>, with a trailing slash.<\/p>\n<p>In production settings, when both the client and the Flask app are served from the same domain, it is just a performance issue: there is an additional network round-trip for each API request. The sequence of events in production is:<\/p>\n<ol>\n<li>Client makes request to <code>https:\/\/myserver.com\/api\/maypp<\/code><\/li>\n<li>Server responds with <code>308 PERMANENT REDIRECT<\/code> to <code>https:\/\/myserver.com\/api\/maypp\/<\/code><\/li>\n<li>Client makes request to <code>https:\/\/myserver.com\/api\/maypp\/<\/code><\/li>\n<li>Server responds with the data.<\/li>\n<\/ol>\n<h3>In Development<\/h3>\n<p>In development settings omitting the final slash may cause CORS problems. My scenario was as follows.<\/p>\n<p>The client is a React application. In debug mode, I serve it from the npm development server (<code>npm start<\/code>) at <code>http:\/\/localhost:3000\/<\/code>. If the client makes an explicit request to <code>https:\/\/myserver.com\/api\/myapp<\/code>, it causes a CORS error, unless the API is configured to accept any origin (which it isn&#8217;t).<\/p>\n<p>To avoid that, we setup a proxy in <code>package.json<\/code> (<a href=\"https:\/\/create-react-app.dev\/docs\/proxying-api-requests-in-development\/\">documentation<\/a>), so the npm development server picks up requests to <code>\/something<\/code>, and fetches data from <code>https:\/\/myserver.com\/something<\/code> behind the scenes. This bypasses the CORS checker: as far as the browser is concerned, we never go out to <code>myserver.com<\/code> and receive everything from <code>localhost<\/code>.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n{\r\n  &quot;name&quot;: &quot;myclient&quot;,\r\n  &quot;version&quot;: &quot;0.1.0&quot;,\r\n  &quot;proxy&quot;: &quot;https:\/\/myserver.com&quot;,\r\n  &quot;private&quot;: true,\r\n  ...\r\n}\r\n<\/pre>\n<p>The sequence of events then becomes:<\/p>\n<ol>\n<li>Client makes request to <code>\/api\/maypp<\/code>.<\/li>\n<li>Local npm server forwards the request to <code>https:\/\/myserver.com\/api\/maypp<\/code><\/li>\n<li>Remote server responds with <code>308 PERMANENT REDIRECT<\/code> to <code>https:\/\/myserver.com\/api\/maypp\/<\/code><\/li>\n<li>Client makes request to <code>https:\/\/myserver.com\/api\/maypp\/<\/code><\/li>\n<li>This causes a CORS error in the browser, since the client is on <code>localhost<\/code> and we are requesting data from a different domain.<\/li>\n<\/ol>\n<p>If the client makes a request to <code>\/api\/myapp\/<\/code> with trailing slash included, everything works as expected:<\/p>\n<ol>\n<li>Client makes request to <code>\/api\/maypp\/<\/code><\/li>\n<li>Local npm server forwards the request to <code>https:\/\/myserver.com\/api\/maypp\/<\/code><\/li>\n<li>Remote server responds with the data, no redirect needed.<\/li>\n<li>Client gets the data, no CORS errors.<\/li>\n<\/ol>\n<p>So, that little trailing slash matters, don&#8217;t forget to include it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>TL;DR On the client side, use trailing slash when requesting the &#8220;default&#8221; resource from a Flask app. Server: # Python flask server at \/var\/www\/api\/myapp app = Flask(__name__) @app.route(&#039;\/&#039;) def get_root_resource(): <a href=\"https:\/\/ikriv.com\/blog\/?p=5240\" class=\"more-link\">[&hellip;]<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"Layout":"","footnotes":""},"categories":[15],"tags":[],"class_list":["entry","author-ikriv","post-5240","post","type-post","status-publish","format-standard","category-webdev"],"_links":{"self":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/5240","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5240"}],"version-history":[{"count":8,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/5240\/revisions"}],"predecessor-version":[{"id":5248,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/5240\/revisions\/5248"}],"wp:attachment":[{"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5240"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5240"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ikriv.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5240"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}