<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Max</title>
    <description>The latest articles on Forem by Max (@rimutaka).</description>
    <link>https://forem.com/rimutaka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F331077%2Fac166450-d1a8-4b3a-b735-ffde8d04809f.jpg</url>
      <title>Forem: Max</title>
      <link>https://forem.com/rimutaka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rimutaka"/>
    <language>en</language>
    <item>
      <title>React to Vue Conversion with Claude, Gemini, and ChatGPT</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Fri, 16 May 2025 05:19:31 +0000</pubDate>
      <link>https://forem.com/rimutaka/react-to-vue-conversion-with-claude-gemini-and-chatgpt-2a</link>
      <guid>https://forem.com/rimutaka/react-to-vue-conversion-with-claude-gemini-and-chatgpt-2a</guid>
      <description>&lt;p&gt;This post covers converting my &lt;a href="https://github.com/rimutaka/bookwormfood" rel="noopener noreferrer"&gt;book reading tracker app&lt;/a&gt; from React to Vue using GitHub Copilot Pro in VSCode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project size:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 JSX components
&lt;/li&gt;
&lt;li&gt;8 asset files (TailwindCSS, images)
&lt;/li&gt;
&lt;li&gt;4 WASM files
&lt;/li&gt;
&lt;li&gt;index.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app has only 3 screens, so it is quite an uncomplicated project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0n166e6mjkovvlyyahzb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0n166e6mjkovvlyyahzb.png" alt="Bookworm.im screens" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Online React to Vue Converters
&lt;/h2&gt;

&lt;p&gt;All online tools offering React to Vue conversion that came up on page one in Google produced unusable garbage.&lt;br&gt;
It was not good enough even for a boilerplate.&lt;/p&gt;

&lt;p&gt;I DO NOT recommend any of these tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tools.w3cub.com/react-to-vue" rel="noopener noreferrer"&gt;https://tools.w3cub.com/react-to-vue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gitloop.com/tool/react-to-vue" rel="noopener noreferrer"&gt;https://www.gitloop.com/tool/react-to-vue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://syntha.ai/converters/react-to-vue" rel="noopener noreferrer"&gt;https://syntha.ai/converters/react-to-vue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.yeschat.ai/gpts-9t557tdtLqi-Code-Transformer-Vue-to-React-and-Vice-Versa" rel="noopener noreferrer"&gt;https://www.yeschat.ai/gpts-9t557tdtLqi-Code-Transformer-Vue-to-React-and-Vice-Versa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chatgpt.com/g/g-4TxR4JVBe-react-vue-converter" rel="noopener noreferrer"&gt;https://chatgpt.com/g/g-4TxR4JVBe-react-vue-converter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot is the default AI Chat choice for VSCode.&lt;br&gt;
You may also consider using &lt;a href="https://www.cursor.com/en" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; or &lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are new to using &lt;a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat" rel="noopener noreferrer"&gt;Copilot Chat window in VSCode&lt;/a&gt;, there is one customization I highly recommend - disable &lt;em&gt;Send on Enter&lt;/em&gt; setting to enter &lt;em&gt;multiline prompts&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open &lt;em&gt;Keyboard Shortcuts&lt;/em&gt; (&lt;code&gt;Ctrl-K&lt;/code&gt; + &lt;code&gt;Ctrl-S&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;workbench.action.chat.submit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Change from &lt;code&gt;Enter&lt;/code&gt; to &lt;code&gt;Shift+Enter&lt;/code&gt; or any other key combination you like&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmaxvpaljt5e55d0kvtmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmaxvpaljt5e55d0kvtmj.png" alt="Keyboard bindings" width="800" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More info:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/orgs/community/discussions/86624" rel="noopener noreferrer"&gt;https://github.com/orgs/community/discussions/86624&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/docs/configure/keybindings#_keyboard-shortcuts-editor" rel="noopener noreferrer"&gt;https://code.visualstudio.com/docs/configure/keybindings#_keyboard-shortcuts-editor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Converting the Entire Project 🙄
&lt;/h2&gt;

&lt;p&gt;Attempts to convert the entire project in one go failed.&lt;br&gt;
All the models clearly prefer the user to break it down into smaller steps, e.g. create scaffolding, improve it, add linters, NPM packages and then convert the components one by one.&lt;/p&gt;

&lt;p&gt;I could have asked the LLM for the action plan, correct it and then ask to execute.&lt;br&gt;
Doing the conversion in small steps seemed like a better approach.&lt;/p&gt;
&lt;h2&gt;
  
  
  Converting a Single React Component 🎉
&lt;/h2&gt;

&lt;p&gt;My &lt;code&gt;NavBar&lt;/code&gt; component had only 71 lines of code and used a single dependency, &lt;code&gt;useAuth0&lt;/code&gt; from &lt;code&gt;@auth0/auth0-react&lt;/code&gt; for authentication.&lt;/p&gt;

&lt;p&gt;Full file: &lt;a href="https://github.com/rimutaka/bookwormfood/blob/react-to-vue-conv/vue/src/components/conv/navbar.js" rel="noopener noreferrer"&gt;https://github.com/rimutaka/bookwormfood/blob/react-to-vue-conv/vue/src/components/conv/navbar.js&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Prompt
&lt;/h3&gt;

&lt;p&gt;It took me a few attempts to come up with a prompt that produced a meaningful conversion with &lt;code&gt;navbar.js&lt;/code&gt; as the context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Convert the current react component into a VueJS component to go into an existing app structure.
Assume that any functions, objects or APIs called from the component exist somewhere.
Copy comments as-is.
Use TypeScript, VueJS composition API.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I asked ChatGPT to improve it. The new prompt was more readable, but made no difference to the end result - all models produced the same output for both.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Convert the provided React component into a Vue 3 Single File Component that fits into an existing Vue application structure.

Use TypeScript and the VueJS Composition API.
Preserve all comments exactly as they appear in the original code.
Assume that any external functions, objects, or APIs referenced in the component are available elsewhere in the project.
Ensure the resulting Vue component is idiomatic and ready for integration into a typical Vue app.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Claude 3.7 Sonnet
&lt;/h3&gt;

&lt;p&gt;The worst result of them all:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;didn't follow the composition API structure&lt;/li&gt;
&lt;li&gt;left placeholders&lt;/li&gt;
&lt;li&gt;unusual formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vue file: &lt;a href="https://github.com/rimutaka/bookwormfood/blob/react-to-vue-conv/vue/src/components/conv/Claude37Sonnet.vue" rel="noopener noreferrer"&gt;https://github.com/rimutaka/bookwormfood/blob/react-to-vue-conv/vue/src/components/conv/Claude37Sonnet.vue&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude 3.5 Sonnet
&lt;/h3&gt;

&lt;p&gt;A much better result with a few minor issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;window.location.port == 80&lt;/code&gt; should be &lt;code&gt;"80"&lt;/code&gt; in TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;process.env.VUE_APP_BUILD_TS&lt;/code&gt; should be &lt;code&gt;import.meta.env.VITE_APP_BUILD_TS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;inconsistent use of &lt;code&gt;''&lt;/code&gt; and &lt;code&gt;""&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vue file: &lt;a href="https://github.com/rimutaka/bookwormfood/blob/react-to-vue-conv/vue/src/components/conv/Claude35Sonnet.vue" rel="noopener noreferrer"&gt;https://github.com/rimutaka/bookwormfood/blob/react-to-vue-conv/vue/src/components/conv/Claude35Sonnet.vue&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini 2.5 Pro
&lt;/h3&gt;

&lt;p&gt;A very similar result to Claude 3.5 Sonnet.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; part was nearly identical with only syntactical and formatting differences for the rest of the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5yzew2rmeozjo2f7526v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5yzew2rmeozjo2f7526v.png" alt="gemini-claude diff" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vue file: &lt;a href="https://github.com/rimutaka/bookwormfood/blob/react-to-vue-conv/vue/src/components/conv/Gemini25Pro.vue" rel="noopener noreferrer"&gt;https://github.com/rimutaka/bookwormfood/blob/react-to-vue-conv/vue/src/components/conv/Gemini25Pro.vue&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ChatGPT-4.1
&lt;/h3&gt;

&lt;p&gt;A nearly identical result to Claude 3.5 Sonnet and Gemini 2.5 Pro.&lt;/p&gt;

&lt;p&gt;Vue file: &lt;a href="https://github.com/rimutaka/bookwormfood/blob/react-to-vue-conv/vue/src/components/conv/ChatGPT41.vue" rel="noopener noreferrer"&gt;https://github.com/rimutaka/bookwormfood/blob/react-to-vue-conv/vue/src/components/conv/ChatGPT41.vue&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;No clear winner - I used Claude, Gemini, and ChatGPT interchangeably.&lt;/p&gt;

&lt;p&gt;The LLMs did about 80% of the work. &lt;br&gt;
The converted code was mostly correct, but not production-ready.&lt;/p&gt;

&lt;p&gt;The full conversion took me 2 days including unrelated bug fixes.&lt;/p&gt;

&lt;p&gt;From this React:&lt;br&gt;
&lt;a href="https://github.com/rimutaka/bookwormfood/tree/54f949bb58dd428fd35f0ff001ac14cfce290395/src" rel="noopener noreferrer"&gt;https://github.com/rimutaka/bookwormfood/tree/54f949b...&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;To this Vue:&lt;br&gt;
&lt;a href="https://github.com/rimutaka/bookwormfood/tree/28e52c26849fb5274bb8f0de9aa55b585da20128/vue/src" rel="noopener noreferrer"&gt;https://github.com/rimutaka/bookwormfood/tree/28e52c2...&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Question
&lt;/h3&gt;

&lt;p&gt;Could one of these &lt;em&gt;brilliant golden retrievers on acid&lt;/em&gt; completely replace me for this task?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not yet.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>vscode</category>
      <category>vue</category>
      <category>ai</category>
    </item>
    <item>
      <title>AWS Lambda with CloudFront: CORS, authorization and caching examples</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 14 Aug 2024 01:50:09 +0000</pubDate>
      <link>https://forem.com/rimutaka/aws-lambda-with-cloudfront-cors-authorization-and-caching-examples-1i9c</link>
      <guid>https://forem.com/rimutaka/aws-lambda-with-cloudfront-cors-authorization-and-caching-examples-1i9c</guid>
      <description>&lt;p&gt;This post is a collection of examples with different configuration options for invoking AWS Lambda via CloudFront.&lt;br&gt;
It demonstrates how configuration changes affect the lambda function invocations and request/response headers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example 1: CORS not required, no authorization headers, no caching
&lt;/h2&gt;

&lt;p&gt;This simple GET request does not require the CORS protocol or authorization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://d9tskged4za87.cloudfront.net/sync.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Function URL configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auth type: AWS_IAM&lt;/li&gt;
&lt;li&gt;CORS disabled (irrelevant as no CORS protocol is involved)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CloudFront configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;origin access with signed requests&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;CachingDisabled&lt;/em&gt; policy&lt;/li&gt;
&lt;li&gt;OPTIONS caching setting is irrelevant since caching is disabled via &lt;em&gt;CachingDisabled&lt;/em&gt; policy&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Managed-AllViewerExceptHostHeader&lt;/em&gt; request policy&lt;/li&gt;
&lt;li&gt;No response policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CloudFront replaces the value of &lt;em&gt;Host&lt;/em&gt; and passes all other headers onto the lambda&lt;/li&gt;
&lt;li&gt;the lambda function handler is invoked for all HTTP request types&lt;/li&gt;
&lt;li&gt;all headers generated by the lambda function are forwarded back to the client&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Request headers
&lt;/h4&gt;

&lt;p&gt;Browser to CloudFront:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /sync.html HTTP/2
Host: d9tskged4za87.cloudfront.net
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br, zstd
Origin: https://localhost:8080
DNT: 1
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Priority: u=0
Pragma: no-cache
Cache-Control: no-cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headers in the payload passed to the lambda function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accept"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accept-encoding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gzip, deflate, br, zstd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accept-language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US,en;q=0.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cache-control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no-cache"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-forwarded-proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-is-android-viewer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-is-desktop-viewer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-is-ios-viewer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-is-mobile-viewer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-is-smarttv-viewer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-is-tablet-viewer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2406:2d40:728d:fa10::c4a:42838"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-asn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14593"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Auckland"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NZ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-country-name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"New Zealand"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-country-region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AUK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-country-region-name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Auckland"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-http-version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-36.85060"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"174.76790"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-postal-code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1010"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-time-zone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pacific/Auckland"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cloudfront-viewer-tls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TLSv1.3:TLS_AES_128_GCM_SHA256:sessionResumed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dnt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pragma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no-cache"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"u=0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sec-fetch-dest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"empty"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sec-fetch-mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sec-fetch-site"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cross-site"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user-agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"via"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0 03e8784cc6fbcd65ff743e9f537e8e88.cloudfront.net (CloudFront)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amz-cf-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wtw2gvr0...o3nk-DQ=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amz-content-sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e3b0c44...852b855"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amz-date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20240812T024912Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amz-security-token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IQoJb3...yM6wdjMEg=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amz-source-account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"512295225992"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amz-source-arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:cloudfront::512295225992:distribution/E3FGXRC3VXQ2IF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amzn-tls-cipher-suite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ECDHE-RSA-AES128-GCM-SHA256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amzn-tls-version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TLSv1.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amzn-trace-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Root=1-66b97828-7217f26e68fb64c806f4daf5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-forwarded-for"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2406:2d40:728d:fa10::c4a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-forwarded-port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"443"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-forwarded-proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Request headers of interest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser to CloudFront &lt;code&gt;Host: d9tskged4za87.cloudfront.net&lt;/code&gt; was replaced by CloudFront&lt;/li&gt;
&lt;li&gt;CloudFront to Lambda: &lt;code&gt;host: mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;origin: https://localhost:8080&lt;/code&gt; is the same for both&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Response headers
&lt;/h4&gt;

&lt;p&gt;Lambda's response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"x-foo-header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text/html; charset=utf-8"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello from client-sync"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isBase64Encoded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cookies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CloudFront to the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/2 200 
content-type: text/html; charset=utf-8
content-length: 22
date: Mon, 12 Aug 2024 02:49:14 GMT
x-amzn-requestid: 28b96338-a3a6-4a6e-aad2-8b7d25a4fe13
x-foo-header: bar
vary: Origin
x-amzn-trace-id: root=1-66b97828-7217f26e68fb64c806f4daf5;parent=717724cd0dc027c2;sampled=0;lineage=a964c7ca:0
x-cache: Miss from cloudfront
via: 1.1 03e8784cc6fbcd65ff743e9f537e8e88.cloudfront.net (CloudFront)
x-amz-cf-pop: LAX3-C3
x-amz-cf-id: Wtw2gvr045gDD6eqIC6s_rDEnugktHpg0YUjmrp2jRuN6PNo3nk-DQ==
X-Firefox-Spdy: h2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response headers of interest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;x-foo-header: bar&lt;/code&gt; custom header was passed from the lambda function to the browser&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x-cache: Miss from cloudfront&lt;/code&gt; CloudFront header is always &lt;code&gt;Miss ...&lt;/code&gt; because the caching is disabled&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example 2: CORS with custom authorization header and no caching
&lt;/h2&gt;

&lt;p&gt;This request requires the CORS protocol, but the authorization is done through a custom header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://d9tskged4za87.cloudfront.net/sync.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Books-Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dummy-book-auth4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Function URL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Auth type: AWS_IAM&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CORS enabled&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In CloudFront (unchanged):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;origin access with signed requests&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;CachingDisabled&lt;/em&gt; policy&lt;/li&gt;
&lt;li&gt;OPTIONS caching is disabled&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Managed-AllViewerExceptHostHeader&lt;/em&gt; request policy&lt;/li&gt;
&lt;li&gt;No response policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Flow changes:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The browser sends the HTTP OPTIONS request first to obtain the CORS headers:
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fno86b905eszx54o8w1pe.png" alt="OPTIONS then GET request sequence" width="800" height="46"&gt;
&lt;/li&gt;
&lt;li&gt;CORS headers are added by AWS Lambda as part of the lambda handler invocation&lt;/li&gt;
&lt;li&gt;The browser sends a GET request after checking the CORS headers&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  OPTIONS request/response
&lt;/h4&gt;

&lt;p&gt;CORS-related request headers for HTTP OPTIONS request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPTIONS /sync.html HTTP/2
Access-Control-Request-Method: GET
Access-Control-Request-Headers: x-books-authorization
Origin: https://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CloudFront OPTIONS response has four CORS headers generated by AWS Lambda without invoking the lambda function handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;access-control-allow-origin: https://localhost:8080
access-control-allow-headers: authorization,content-type,x-books-authorization
access-control-allow-methods: GET,POST
access-control-allow-credentials: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  GET request/response
&lt;/h4&gt;

&lt;p&gt;The OPTIONS request is followed by a GET request with a custom &lt;em&gt;X-Books-Authorization&lt;/em&gt; header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /sync.html HTTP/2
X-Books-Authorization: dummy-book-auth4
Origin: https://localhost:8080
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lambda function handler receives the &lt;em&gt;X-Books-Authorization&lt;/em&gt; header inside the GET request payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-books-authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dummy-book-auth4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lambda's response is identical to the previous GET example with no CORS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"x-foo-header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text/html; charset=utf-8"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello from client-sync"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isBase64Encoded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cookies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CloudFront GET response has two CORS headers added to the Lambda's response by AWS Lambda (not the handler):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;access-control-allow-origin: https://localhost:8080
access-control-allow-credentials: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 3: CORS with Authorization header and caching
&lt;/h3&gt;

&lt;p&gt;This request uses the &lt;em&gt;Authorization&lt;/em&gt; header and requires the CORS protocol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://d9tskged4za87.cloudfront.net/sync.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dummy-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Function URL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Auth type: NONE&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CORS enabled&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In CloudFront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;origin access with unsigned requests&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;custom caching policy that includes the &lt;em&gt;Authorization&lt;/em&gt; header&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OPTIONS caching is enabled&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Managed-AllViewerExceptHostHeader&lt;/em&gt; request policy&lt;/li&gt;
&lt;li&gt;No response policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Flow changes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Lambda function is public (Auth type: NONE)&lt;/li&gt;
&lt;li&gt;CloudFront no longer uses the &lt;em&gt;Authorization&lt;/em&gt; header (no request signing)&lt;/li&gt;
&lt;li&gt;repeat requests with the same value for the &lt;em&gt;Authorization&lt;/em&gt; header are served from the CloudFront cache&lt;/li&gt;
&lt;li&gt;repeat OPTIONS requests are served from the CloudFront cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the request/response headers are nearly identical to the previous example, except for the common &lt;code&gt;Authorization&lt;/code&gt; header replacing the custom &lt;code&gt;X-Books-Authorization&lt;/code&gt; header.&lt;br&gt;
The &lt;em&gt;Authorization&lt;/em&gt; header is passed to the lambda function handler as part of the payload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OPTIONS request/response:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identical to the previous example&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  GET request headers
&lt;/h4&gt;

&lt;p&gt;From browser to CloudFront:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /sync.html HTTP/2
Host: d9tskged4za87.cloudfront.net
Authorization: dummy-auth
Origin: https://localhost:8080
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;Authorization&lt;/em&gt; header was added to the lambda handler payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dummy-auth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Caching
&lt;/h4&gt;

&lt;p&gt;Initial OPTIONS/GET requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;both OPTIONS and GET return &lt;code&gt;x-cache: Miss from cloudfront&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;AWS Lambda (not the handler) responds to OPTIONS request&lt;/li&gt;
&lt;li&gt;the lambda handler is invoked for the GET request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repeat OPTIONS/GET requests with the same &lt;em&gt;Authorization&lt;/em&gt; value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;both return &lt;code&gt;x-cache: Hit from cloudfront&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;no lambda invocations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repeat OPTIONS/GET requests with different &lt;em&gt;Authorization&lt;/em&gt; values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OPTIONS returns &lt;code&gt;x-cache: Hit from cloudfront&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;GET returns &lt;code&gt;x-cache: Miss from cloudfront&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;the lambda handler is invoked for the GET request&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>webdev</category>
      <category>lambda</category>
      <category>http</category>
    </item>
    <item>
      <title>AWS Lambda with CloudFront configuration guide</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 14 Aug 2024 01:28:32 +0000</pubDate>
      <link>https://forem.com/rimutaka/aws-lambda-with-cloudfront-configuration-guide-27lj</link>
      <guid>https://forem.com/rimutaka/aws-lambda-with-cloudfront-configuration-guide-27lj</guid>
      <description>&lt;p&gt;This post explores different configuration options for invoking AWS Lambda via CloudFront to demonstrate how different CloudFront and Lambda Function URL settings affect CORS and authorization headers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;AWS Lambda Functions can be invoked via a public &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html" rel="noopener noreferrer"&gt;AWS Lambda Function URL&lt;/a&gt; with an HTTP call from a browser script, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lambdaResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two possible downsides to this type of invocation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;you cannot use a custom domain&lt;/li&gt;
&lt;li&gt;no server-side caching&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Calling your lambdas via CloudFront solves both of these issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration complexity
&lt;/h3&gt;

&lt;p&gt;There is quite a bit of configuration to be done upfront to make a Lambda function work with CloudFront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda Function URL access control and CORS&lt;/li&gt;
&lt;li&gt;CloudFront origin and behaviors&lt;/li&gt;
&lt;li&gt;CloudFront caching policy&lt;/li&gt;
&lt;li&gt;CloudFront origin request policy&lt;/li&gt;
&lt;li&gt;CloudFront response headers policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide complements the &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html#concept_lambda_function_url" rel="noopener noreferrer"&gt;official AWS documentation&lt;/a&gt; with examples and shortcuts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core concepts
&lt;/h2&gt;

&lt;p&gt;This section explains CloudFront terms and concepts that affect Lambda, CORS and the &lt;em&gt;Authorization&lt;/em&gt; header.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;General&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudFront forwards always forwards uncached HTTP OPTIONS requests to the Lambda URL&lt;/li&gt;
&lt;li&gt;CloudFront can cache OPTIONS responses&lt;/li&gt;
&lt;li&gt;CloudFront can drop or overwrite some of the request and response headers&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Authorization&lt;/em&gt;, &lt;em&gt;Host&lt;/em&gt; and other headers are used by AWS in communication between CloudFront and Lambda and may conflict with the same headers used by the web client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CORS headers&lt;/strong&gt; can be added in three different places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;by the code inside the lambda function&lt;/li&gt;
&lt;li&gt;by AWS if Lambda Function URL CORS were configured&lt;/li&gt;
&lt;li&gt;by CloudFront via &lt;em&gt;Response Headers Policy&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Origin access control&lt;/strong&gt; settings tell CloudFront if it should sign requests sent to the Lambda Function URL.&lt;br&gt;
Signing requests &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html" rel="noopener noreferrer"&gt;takes over the &lt;em&gt;Authorization&lt;/em&gt; header&lt;/a&gt; and drops the value sent by the client app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching policy&lt;/strong&gt; settings of a &lt;em&gt;Behavior&lt;/em&gt; tell CloudFront which headers to use as caching keys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;headers included in the caching key are passed onto the lambda&lt;/li&gt;
&lt;li&gt;you have to include your authorization key to avoid serving one user's response to another user&lt;/li&gt;
&lt;li&gt;even if you include the &lt;em&gt;Authorization&lt;/em&gt; header in the key it may be taken over by the AWS signature configured in the &lt;em&gt;Origin access control&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;you do not have to have a caching policy for the CORS protocol to work&lt;/li&gt;
&lt;li&gt;AWS recommends &lt;em&gt;CachingDisabled&lt;/em&gt; policy for Lambda URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Origin request policy&lt;/strong&gt; settings tell CloudFront which headers to forward to the lambda:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not all headers are forwarded by default&lt;/li&gt;
&lt;li&gt;you should not forward the &lt;em&gt;Host&lt;/em&gt; header (it returns an error at runtime if you do)&lt;/li&gt;
&lt;li&gt;CloudFront may drop or replace some headers&lt;/li&gt;
&lt;li&gt;this policy &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/understanding-how-origin-request-policies-and-cache-policies-work-together.html" rel="noopener noreferrer"&gt;depends on the options selected in the Caching Policy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;AllViewerExceptHostHeader&lt;/em&gt; policy works fine with CORS and is the default choice for Lambda URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Response headers policy&lt;/strong&gt; tells CloudFront what headers to add to the response:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you can choose &lt;em&gt;None&lt;/em&gt; if the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html?icmpid=docs_lambda_help#urls-cors" rel="noopener noreferrer"&gt;lambda function handles the CORS response&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;you can choose a suitable managed CORS policy from the list to complement or overwrite the lambda's response&lt;/li&gt;
&lt;li&gt;you can create a custom policy to complement or overwrite the lambda's response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Header quotas&lt;/strong&gt; set &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-custom-headers" rel="noopener noreferrer"&gt;limits&lt;/a&gt; to how much data you can put into custom headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Max length of a header value: 1,783 characters&lt;/li&gt;
&lt;li&gt;Max length of all headers: 10,240 characters&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lambda Function URL configuration for CloudFront
&lt;/h2&gt;

&lt;p&gt;Let's assume that you already have a working Lambda function with a configured URL for public access and we need to make it work with CloudFront.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda URL access control config
&lt;/h2&gt;

&lt;p&gt;The following config lets CloudFront access a Lambda function via its URL in the most secure way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwclo5x1ge0rkco5pao3d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwclo5x1ge0rkco5pao3d.png" alt="Access control setting screenshot" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will add the CloudFront IAM policy to the Lambda at a later stage.&lt;/p&gt;

&lt;p&gt;Setting your function URL access control to &lt;em&gt;Auth type: NONE&lt;/em&gt; allows anyone, including CloudFront to invoke it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda URL CORS headers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Enabling CORS headers&lt;/strong&gt; in Lambda Function URL settings intercepts all HTTP OPTIONS requests sent to the function and returns the configured response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rz9y4jmm1v8jtif8mfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rz9y4jmm1v8jtif8mfm.png" alt="Lambda URL CORS" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disabling CORS headers&lt;/strong&gt; forwards HTTP OPTIONS requests to the function handler.&lt;/p&gt;

&lt;p&gt;CORS headers can be configured in CloudFront's &lt;em&gt;Response Headers Policy&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;CloudFront can cache HTTP OPTIONS responses regardless of which of the above strategies of handling CORS you choose.&lt;/p&gt;

&lt;h2&gt;
  
  
  CloudFront configuration
&lt;/h2&gt;

&lt;p&gt;A Lambda function should be added as an &lt;em&gt;Origin&lt;/em&gt; of a CloudFront distribution.&lt;/p&gt;

&lt;p&gt;In this example, &lt;em&gt;client-sync lambda&lt;/em&gt; origin is linked to &lt;em&gt;/sync.html&lt;/em&gt; path. For example, if a client app makes a &lt;code&gt;fetch()&lt;/code&gt; call to &lt;code&gt;https://d9tskged4za87.cloudfront.net/sync.html&lt;/code&gt; the request will be forwarded to &lt;em&gt;client-sync lambda&lt;/em&gt; for processing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5iuhdxut44sbxc8qnisn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5iuhdxut44sbxc8qnisn.png" alt="CloudFront Origins" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdblt47zb1eke78keahz6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdblt47zb1eke78keahz6.png" alt="CloudFront Behaviors" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Origin configuration
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jgu702bc48gv6wrn3xh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jgu702bc48gv6wrn3xh.png" alt="create origin" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;origin domain:&lt;/strong&gt; copy-paste the domain of the Lambda Function URL, e.g. &lt;code&gt;mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;origin path:&lt;/strong&gt; leave blank&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;name:&lt;/strong&gt; give it an informative name, spaces are OK; it will not be used as an ID&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Origin access control affects the use of &lt;em&gt;Authorization&lt;/em&gt; header
&lt;/h3&gt;

&lt;p&gt;Create a new &lt;em&gt;Origin Access Policy&lt;/em&gt; that can be shared between multiple lambda functions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbacpp8cavjihhz8u8j66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbacpp8cavjihhz8u8j66.png" alt="Origin Access Control" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Signing CloudFront requests to the lambda function URL also uses the &lt;em&gt;Authorization&lt;/em&gt; header which can create a conflict if the web client uses the &lt;em&gt;Authorization&lt;/em&gt; header for its authorization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not sign requests&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudFront passes the &lt;em&gt;Authorization&lt;/em&gt; header from the browser to the lambda&lt;/li&gt;
&lt;li&gt;the lambda must have &lt;em&gt;Auth type: NONE&lt;/em&gt; and a public access policy enabled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sign requests&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Authorization&lt;/em&gt; header from the browser is dropped&lt;/li&gt;
&lt;li&gt;CloudFront uses the &lt;em&gt;Authorization&lt;/em&gt; header to sign requests to the lambda&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Authorization&lt;/em&gt; header is excluded from the lambda's payload&lt;/li&gt;
&lt;li&gt;the lambda can have &lt;em&gt;Auth type: AWS_IAM&lt;/em&gt; and no public access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Do not override authorization header&lt;/strong&gt;&lt;br&gt;
This is a combination of the two previous policies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sign if there is no &lt;em&gt;Authorization&lt;/em&gt; header coming from the browser&lt;/li&gt;
&lt;li&gt;do not sign if there is one coming from the browser&lt;/li&gt;
&lt;li&gt;the lambda must have &lt;em&gt;Auth type: NONE&lt;/em&gt; and a public access policy enabled for this option to work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;em&gt;Origin Access&lt;/em&gt; policy can be edited later from the sidebar under &lt;em&gt;Security / Origin access&lt;/em&gt; menu.&lt;/p&gt;

&lt;h4&gt;
  
  
  Lambda access policy
&lt;/h4&gt;

&lt;p&gt;Copy-paste and run the template generated by the console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tnxbuiw78z3fkk1bjqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tnxbuiw78z3fkk1bjqn.png" alt="Lambda access policy" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you go back to your lambda function after running the &lt;code&gt;aws lambda add-permission&lt;/code&gt; command, you should see the newly added policy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxcqxjafmtn07uog05jz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxcqxjafmtn07uog05jz.png" alt="Access control policy screenshot" width="800" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The policy should have your account and distribution IDs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtsxcktq07v6hya8j8im.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtsxcktq07v6hya8j8im.png" alt="Access control policy contents" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Behavior configuration
&lt;/h3&gt;

&lt;p&gt;Create a new &lt;em&gt;behavior&lt;/em&gt; with the path to the endpoint that invokes the Lambda (1) and the name of origin you created for the lambda earlier (2) as in the following example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn9jl1dp62yyoy3ea158.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxn9jl1dp62yyoy3ea158.png" alt="Cloudfront behavior settings" width="800" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The address of this endpoint would be &lt;code&gt;https://[CF distr ID].cloudfront.net/sync.html&lt;/code&gt; or &lt;code&gt;https://[custom domain]/sync.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You must select an option with the &lt;em&gt;HTTP OPTIONS&lt;/em&gt; method for CORS to work.&lt;/p&gt;

&lt;p&gt;Caching OPTIONS requests prevents sending repeat OPTIONS requests to the lambda. This option depends on the selection of the caching policy discussed further in this guide.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs5mb1an16l2hbdophh7h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs5mb1an16l2hbdophh7h.png" alt="Allowed HTTP methods screen" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the policies section of the form select:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache policy&lt;/strong&gt; &lt;em&gt;CachingDisabled&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origin request policy&lt;/strong&gt; &lt;em&gt;AllViewerExceptHostHeader&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response headers policy&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;select None if the Lambda Function URL CORS option was configured, or&lt;/li&gt;
&lt;li&gt;create a new policy (more on this is further in the document), select None for now anyway&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/rimutaka/posts/master/lambda-url-with-cloudfront/cf-policies.png" rel="noopener noreferrer"&gt;Policy selection&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above configuration should be sufficient to correctly process CORS and authorization requests to a lambda function via CloudFront.&lt;/p&gt;

&lt;h2&gt;
  
  
  CloudFront policies and CORS headers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Caching policies
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Caching disabled
&lt;/h4&gt;

&lt;p&gt;AWS &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html#managed-cache-policy-caching-disabled" rel="noopener noreferrer"&gt;recommends disabling caching for Lambda functions&lt;/a&gt; because most of the requests are unique and should not be cached.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73wopkdbxtym7c1li402.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73wopkdbxtym7c1li402.png" alt="Caching policy disabled" width="708" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some headers, including the &lt;em&gt;Authorization&lt;/em&gt; header, are &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html#request-custom-headers-behavior" rel="noopener noreferrer"&gt;removed by CloudFront if caching is disabled&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Caching enabled with Authorization header
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Authorization&lt;/em&gt; header gets &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html#RequestCustomClientAuth" rel="noopener noreferrer"&gt;special treatment&lt;/a&gt; from AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CloudFront does not pass it to lambda functions for GET/HEAD requests unless it is included in the caching policy&lt;/li&gt;
&lt;li&gt;CloudFront forwards it for &lt;em&gt;DELETE&lt;/em&gt;, &lt;em&gt;PATCH&lt;/em&gt;, &lt;em&gt;POST&lt;/em&gt;, and &lt;em&gt;PUT&lt;/em&gt; HTTP methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above rule is affected by a different setting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS uses the &lt;em&gt;Authorization&lt;/em&gt; header for IAM authentication, so even if you have &lt;em&gt;Auth type: AWS_IAM&lt;/em&gt; in the Function URL settings the &lt;em&gt;Authorization&lt;/em&gt; header will not reach the lambda no matter what the caching policy says&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One workaround for passing &lt;em&gt;Authorization&lt;/em&gt; header to lambdas is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;disable Lambda Function URL IAM authentication with &lt;em&gt;Auth type: NONE&lt;/em&gt;,&lt;/li&gt;
&lt;li&gt;add &lt;em&gt;Authorization&lt;/em&gt; header to the caching policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If disabling Lambda Function URL IAM authentication is not an option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep &lt;em&gt;Auth type: AWS_IAM&lt;/em&gt; in the Function URL settings&lt;/li&gt;
&lt;li&gt;use a custom header to pass the authentication value from your web client to your lambda, e.g. &lt;code&gt;x-myapp-auth: Bearer some-long-JWT-value&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;always add the custom authorization header to the caching policy to prevent responses to one authenticated user served to someone else&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Origin request policy
&lt;/h2&gt;

&lt;p&gt;Use &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html#managed-origin-request-policy-all-viewer-except-host-header" rel="noopener noreferrer"&gt;AllViewerExceptHostHeader&lt;/a&gt; or create a custom policy that excludes the &lt;em&gt;Host&lt;/em&gt; header from being forwarded to lambda.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnepn8y6foiknx1yjvv11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnepn8y6foiknx1yjvv11.png" alt="Origin request policy" width="800" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;AllViewer-&lt;/em&gt; part of the name of that origin request policy is misleading because CloudFront removes and repurposes some of the headers, e.g. &lt;em&gt;Authorization&lt;/em&gt; and &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html#request-custom-headers-behavior" rel="noopener noreferrer"&gt;many others&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Response headers policy
&lt;/h2&gt;

&lt;p&gt;Use this policy only if you want CloudFront to add CORS and other headers to the response on top or instead of what the lambda returns in its response. For example, this sample policy adds all the necessary CORS headers to let scripts running in your local DEV environment (&lt;a href="https://localhost:8080" rel="noopener noreferrer"&gt;https://localhost:8080&lt;/a&gt;) call the lambda function:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ma1jbc1ow6qeneeetfj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ma1jbc1ow6qeneeetfj.png" alt="sample response policy" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use one of the &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-response-headers-policies.html" rel="noopener noreferrer"&gt;AWS-managed CORS policies&lt;/a&gt; that add the &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt; header to allow scripts from any domain to access your lambda function. &lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;AWS Lambda&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rimutaka/lambda-debugger-runtime-emulator" rel="noopener noreferrer"&gt;AWS Lambda debugging tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html#urls-auth-iam" rel="noopener noreferrer"&gt;Lambda URL access control&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CloudFront&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cache-key-understand-cache-policy.html#cache-policy-headers" rel="noopener noreferrer"&gt;Cache policy headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html" rel="noopener noreferrer"&gt;Request and response behavior for custom origins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/add-origin-custom-headers.html#add-origin-custom-headers-forward-authorization" rel="noopener noreferrer"&gt;Configure CloudFront to forward the Authorization header&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CORS&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;CORS overview on MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CORS request headers: 

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method" rel="noopener noreferrer"&gt;Access-Control-Request-Method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers" rel="noopener noreferrer"&gt;Access-Control-Request-Headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin" rel="noopener noreferrer"&gt;Origin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Required CORS response headers: 

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods" rel="noopener noreferrer"&gt;Access-Control-Allow-Methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers" rel="noopener noreferrer"&gt;Access-Control-Allow-Headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin" rel="noopener noreferrer"&gt;Access-Control-Allow-Origin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Optional CORS response headers: 

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age" rel="noopener noreferrer"&gt;Access-Control-Max-Age&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers" rel="noopener noreferrer"&gt;Access-Control-Expose-Headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials" rel="noopener noreferrer"&gt;Access-Control-Allow-Credentials&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>lambda</category>
      <category>aws</category>
      <category>security</category>
    </item>
    <item>
      <title>AWS Lambda Function URL with CORS explained by example</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 14 Aug 2024 00:25:55 +0000</pubDate>
      <link>https://forem.com/rimutaka/aws-lambda-function-url-with-cors-explained-by-example-14df</link>
      <guid>https://forem.com/rimutaka/aws-lambda-function-url-with-cors-explained-by-example-14df</guid>
      <description>&lt;p&gt;This post explores different configuration options for invoking AWS Lambda functions via a URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why we need Cross-Origin Resource Sharing (CORS) for Lambda Function URLs
&lt;/h3&gt;

&lt;p&gt;AWS Lambda Functions can be invoked via a public &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html" rel="noopener noreferrer"&gt;AWS Lambda Function URL&lt;/a&gt; with an HTTP call from a browser script, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lambdaResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the calling script and the lambda URL are running on different domains, the web browser would require the right &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;CORS headers&lt;/a&gt; (also called &lt;a href="https://fetch.spec.whatwg.org/#http-cors-protocol" rel="noopener noreferrer"&gt;CORS protocol&lt;/a&gt;) to be present for the call to succeed.&lt;/p&gt;

&lt;p&gt;The CORS protocol for the above &lt;code&gt;fetch()&lt;/code&gt; involves sending an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS" rel="noopener noreferrer"&gt;HTTP OPTIONS request&lt;/a&gt; to the lambda with these three CORS headers to confirm that the lambda accepts requests from the script's domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Origin: https://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lambda is expected to respond with this set of headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Origin: https://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 1: Returning CORS from the lambda function handler
&lt;/h3&gt;

&lt;p&gt;It is not hard to return a few headers from your lambda handler. For example, these 4 lines of Rust code do the job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HeaderMap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="nf"&gt;.append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Access-Control-Allow-Origin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;HeaderValue&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://localhost:8080"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="nf"&gt;.append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Access-Control-Allow-Methods"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;HeaderValue&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="nf"&gt;.append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Access-Control-Allow-Headers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nn"&gt;HeaderValue&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"authorization"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the other hand, it is an extra coding, testing and maintenance effort. Any changes to the CORS settings would require code changes as well.&lt;/p&gt;

&lt;p&gt;If your client app sends one OPTIONS request for every GET/POST it doubles the number of invocations.&lt;br&gt;
See the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age" rel="noopener noreferrer"&gt;Access-Control-Max-Age header&lt;/a&gt; to reduce repeat OPTIONS calls by the same client.&lt;/p&gt;

&lt;p&gt;Returning CORS from the lambda function handler may be a good option if you need extra control over processing HTTP OPTIONS requests.&lt;/p&gt;
&lt;h3&gt;
  
  
  Option 2: Configuring CORS headers in AWS Lambda settings
&lt;/h3&gt;

&lt;p&gt;Function URLs can be configured to let AWS handle CORS preflight requests (HTTP OPTIONS method) and add necessary headers to all the other HTTP methods after your lambda returns its response.&lt;/p&gt;

&lt;p&gt;This is a simpler and more reliable option that does not require any changes to the function handler.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4r08rbmjr8p9pil2w64w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4r08rbmjr8p9pil2w64w.png" alt="CORS config option checkbox" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Custom domain names for Lambda URLs
&lt;/h2&gt;

&lt;p&gt;It is not possible to use custom domain names with Lambda URLs because the web server that invokes the lambda relies on the &lt;em&gt;Host&lt;/em&gt; HTTP header to identify which lambda to invoke, e.g. &lt;code&gt;host: mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CNAME example&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lambda URL: &lt;em&gt;mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;CNAME record: &lt;code&gt;lambda.example.com CNAME mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;host&lt;/em&gt; header sent to AWS: &lt;code&gt;host: lambda.example.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The web server handling that request at the AWS end would not know which lambda function to invoke by looking at &lt;code&gt;host: lambda.example.com&lt;/code&gt; and would return an error or time out.&lt;/p&gt;

&lt;p&gt;Use &lt;em&gt;ApiGateway&lt;/em&gt; or &lt;em&gt;CloudFront&lt;/em&gt; as the proxy if using lambda Function URLs is not an option.&lt;/p&gt;
&lt;h2&gt;
  
  
  Access control configuration for Lambda Function URL
&lt;/h2&gt;

&lt;p&gt;Let's assume that our function is exposed to the internet and does not require AWS IAM authentication.&lt;/p&gt;

&lt;p&gt;The following config allows public access to the function via its URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkczygyygta5uzixjvxmb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkczygyygta5uzixjvxmb.png" alt="Access control setting screenshot" width="800" height="228"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk26ngbtbig9y931u14q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk26ngbtbig9y931u14q.png" alt="Access control policy screenshot" width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You need both the &lt;em&gt;Auth type: NONE&lt;/em&gt; setting and a resource-based policy statement to allow public access.&lt;br&gt;
Having one setting without the other results in &lt;em&gt;403 Permission Denied&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;AWS automatically adds the required &lt;em&gt;FunctionURLAllowPublicAccess&lt;/em&gt; access policy to the Lambda function when you choose &lt;em&gt;Auth type: NONE&lt;/em&gt; in the console.&lt;/p&gt;
&lt;h2&gt;
  
  
  Request/response examples for different configuration options in detail
&lt;/h2&gt;

&lt;p&gt;This section contains examples of HTTP headers exchanged between the web browser, AWS and the lambda function to help us understand how different configuration options affect the headers.&lt;/p&gt;
&lt;h3&gt;
  
  
  Example 1: Lambda URL request/response without CORS protocol
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Enable public access to your lambda function URL as explained earlier&lt;/li&gt;
&lt;li&gt;Do not enable CORS settings&lt;/li&gt;
&lt;li&gt;Send a request to the lambda URL displayed in the config screen&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your Function URL config should look similar to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04jtyfrhzuhzzivdqt0p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04jtyfrhzuhzzivdqt0p.png" alt="Basic lambda URL config screenshot" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A sample lambda URL call that does not require the CORS protocol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lambdaResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your request may look similar to this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET / HTTP/1.1
Host: mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br, zstd
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=4
Pragma: no-cache
Cache-Control: no-cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lambda function receives all the headers sent by the browser as part of the request payload and some additional AWS headers (see 6 headers starting with &lt;em&gt;x-&lt;/em&gt; at the end of the list):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accept"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accept-encoding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gzip, deflate, br, zstd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accept-language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US,en;q=0.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"foo-bar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cache-control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no-cache"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dnt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pragma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no-cache"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"u=4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sec-fetch-dest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sec-fetch-mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"navigate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sec-fetch-site"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"same-origin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sec-fetch-user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"?1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"upgrade-insecure-requests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user-agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amzn-tls-cipher-suite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TLS_AES_128_GCM_SHA256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amzn-tls-version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TLSv1.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amzn-trace-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Root=1-66b44063-116ea8667f4b70e405b1b19a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-forwarded-for"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"222.154.108.14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-forwarded-port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"443"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-forwarded-proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Function URL configuration with &lt;em&gt;CORS: not enabled&lt;/em&gt; option invokes the lambda for all HTTP methods, including OPTIONS and passes all browser headers to the lambda. &lt;/p&gt;

&lt;p&gt;Use this configuration if your lambda handles responses to the &lt;em&gt;HTTP OPTIONS&lt;/em&gt; method with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;CORS protocol&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: CORS headers added by lambda handler
&lt;/h3&gt;

&lt;p&gt;This example has the same Lambda URL configuration as in the previous example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04jtyfrhzuhzzivdqt0p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F04jtyfrhzuhzzivdqt0p.png" alt="Basic lambda URL config screenshot" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This time we include an optional &lt;em&gt;Authorization&lt;/em&gt; header to trigger the CORS protocol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lambdaResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new request/response flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a browser script running on &lt;a href="https://localhost:8080" rel="noopener noreferrer"&gt;https://localhost:8080&lt;/a&gt; attempts to &lt;code&gt;fetch()&lt;/code&gt; from the lambda's URL&lt;/li&gt;
&lt;li&gt;the browser initiates the CORS protocol by sending an HTTP OPTIONS request to the lambda&lt;/li&gt;
&lt;li&gt;the lambda replies with the necessary CORS headers&lt;/li&gt;
&lt;li&gt;the browser sends the GET request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The HTTP OPTIONS request would be very similar to the previous example with the addition of a few CORS headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;origin&lt;/strong&gt;: &lt;a href="https://localhost:8080" rel="noopener noreferrer"&gt;https://localhost:8080&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;access-control-request-headers&lt;/strong&gt;: authorization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;access-control-request-method&lt;/strong&gt;: GET&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This OPTIONS request can be translated as the browser asking our lambda: can I send you a GET request with the &lt;em&gt;authorization&lt;/em&gt; header from a web page located at &lt;em&gt;&lt;a href="https://localhost:8080" rel="noopener noreferrer"&gt;https://localhost:8080&lt;/a&gt;&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;If the lambda replies &lt;em&gt;Yes&lt;/em&gt;, the browser sends the GET request. If the lambda replies something other than &lt;em&gt;Yes&lt;/em&gt;, the GET request is never sent and the script gets a CORS error.&lt;/p&gt;

&lt;p&gt;Our lambda would see this list of headers for the above OPTIONS request (see 3 lines at the end for CORS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accept"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accept-encoding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gzip, deflate, br, zstd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accept-language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US,en;q=0.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cache-control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no-cache"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dnt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pragma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no-cache"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"u=4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sec-fetch-dest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"empty"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sec-fetch-mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sec-fetch-site"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cross-site"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user-agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amzn-tls-cipher-suite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TLS_AES_128_GCM_SHA256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amzn-tls-version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TLSv1.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-amzn-trace-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Root=1-66b42e1d-56f894f802ecd9bc345ef57a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-forwarded-for"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"222.154.108.14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-forwarded-port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"443"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x-forwarded-proto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"origin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access-control-request-headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access-control-request-method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assuming that our lambda function can handle CORS protocol and is happy with the request, the response would have the necessary CORS headers starting with &lt;em&gt;access-control-allow-*&lt;/em&gt; to tell the browser that the lambda is happy to receive the GET request, as in this example (see 4 lines at the end for CORS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
Date: Thu, 08 Aug 2024 18:18:41 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 5
Connection: keep-alive
x-amzn-RequestId: 9f50a878-597b-4fd9-8a07-717e078a2ab0
X-Amzn-Trace-Id: root=1-66b50c01-207a612c605445d738495a39;parent=5ff8d4e2feb9297b;sampled=0;lineage=a964c7ca:0
access-control-allow-origin: https://localhost:8080
access-control-allow-headers: authorization
access-control-allow-methods: GET
access-control-allow-credentials: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser receives the above response and follows with HTTP GET asking the lambda to do some work, e.g. return some data.&lt;br&gt;
The GET request contains the &lt;em&gt;Authorization&lt;/em&gt; header with a truncated JWT token (see the last line):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET / HTTP/1.1
Host: mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br, zstd
Origin: https://localhost:8080
DNT: 1
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Priority: u=0
Pragma: no-cache
Cache-Control: no-cache
Authorization: Bearer eyJhbGci...ba3mp4OQ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lambda does its work and returns some payload with the following headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
Date: Thu, 08 Aug 2024 22:44:38 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 22
Connection: keep-alive
x-amzn-RequestId: 7307ef70-161c-431c-a7ac-b6ef4838549e
Vary: Origin
X-Amzn-Trace-Id: root=1-66b54a56-2822789c217e2f0847d6b03f;parent=0e288bd87a583781;sampled=0;lineage=a964c7ca:0
Access-Control-Allow-Origin: https://localhost:8080
Access-Control-Allow-Credentials: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above response has to contain &lt;code&gt;Access-Control-Allow-Origin: https://localhost:8080&lt;/code&gt; and &lt;code&gt;Access-Control-Allow-Credentials: true&lt;/code&gt; headers for the browser to accept it (2 lines at the end).&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: CORS request/response headers added by AWS
&lt;/h3&gt;

&lt;p&gt;This example shows how CORS headers can be configured out of the lambda handler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allow HTTP GET/POST requests containing &lt;em&gt;Authorization&lt;/em&gt; and other headers from &lt;a href="https://localhost:8080" rel="noopener noreferrer"&gt;https://localhost:8080&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;allow sending credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rz9y4jmm1v8jtif8mfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6rz9y4jmm1v8jtif8mfm.png" alt="Lambda CORS config" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The client calls the lambda URL with the same &lt;em&gt;fetch()&lt;/em&gt; as before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lambdaResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://mq75dt64puwxk3u6gjw2rhak4m0bcmmi.lambda-url.us-east-1.on.aws/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser does the same CORS protocol as before with the same CORS headers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnflbuou35ibv9yxyyzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcnflbuou35ibv9yxyyzo.png" alt="Browser request sequence with OPTIONS/GET" width="800" height="39"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unlike the previous example where the OPTIONS request was handled by the lambda handler, no lambda invocation takes place for HTTP OPTIONS requests which are handled by AWS.&lt;/p&gt;

&lt;p&gt;This response was generated by AWS and contains the settings configured in the CORS section of the Function URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
Date: Thu, 08 Aug 2024 18:53:07 GMT
Content-Type: application/json
Content-Length: 0
Connection: keep-alive
x-amzn-RequestId: 215426b9-920c-4d1f-b994-54ccd29b2612
Vary: Origin
Access-Control-Allow-Origin: https://localhost:8080
Access-Control-Allow-Headers: authorization,content-type,x-books-authorization
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Credentials: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last four lines of response tell the browser it may continue with more GET/POST requests.&lt;/p&gt;

&lt;p&gt;Unlike the previous example, the lambda handler was not involved in handling the CORS protocol - it was processed by AWS outside of the function handler.&lt;br&gt;
This is an easier way than handling CORS inside the lambda handler.&lt;/p&gt;
&lt;h2&gt;
  
  
  A few "gotchas"
&lt;/h2&gt;

&lt;p&gt;This section lists a few minor things that can suck up a lot of your time.&lt;/p&gt;
&lt;h3&gt;
  
  
  Add one header per line in the CORS configuration form
&lt;/h3&gt;

&lt;p&gt;Since the HTTP headers are sent as a comma-separated list it seems logical to enter the entire list in a single &lt;em&gt;Allow headers&lt;/em&gt; box (red highlight).&lt;br&gt;
AWS will let you save the invalid config and produce an incorrect response to the OPTIONS request later.&lt;/p&gt;

&lt;p&gt;Enter one header per line (green highlight). Remember that header names are case-insensitive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ko8elgj5h7i2vs46ylp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ko8elgj5h7i2vs46ylp.png" alt="lambda headers - one per line example" width="765" height="336"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Don't allow &lt;code&gt;localhost&lt;/code&gt; CORS in production
&lt;/h3&gt;

&lt;p&gt;It is not trivial to exploit this, but it is possible if the site has XSS vulnerabilities or a reverse proxy is involved.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://stackoverflow.com/questions/39042799/cors-localhost-as-allowed-origin-in-production" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/39042799/cors-localhost-as-allowed-origin-in-production&lt;/a&gt; for detailed explanations.&lt;/p&gt;
&lt;h3&gt;
  
  
  Adding and deleting &lt;em&gt;FunctionURLAllowPublicAccess&lt;/em&gt; access policy
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;FunctionURLAllowPublicAccess&lt;/em&gt; policy is added by AWS when you choose &lt;em&gt;Auth type: NONE&lt;/em&gt; for the Function URL.&lt;/p&gt;

&lt;p&gt;It is not removed if you change to &lt;em&gt;Auth type: AWS_IAM&lt;/em&gt;, but the public access is no longer available. See &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html#urls-auth-none" rel="noopener noreferrer"&gt;AWS docs&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk26ngbtbig9y931u14q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk26ngbtbig9y931u14q.png" alt="Access control policy screenshot" width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Removing &lt;em&gt;FunctionURLAllowPublicAccess&lt;/em&gt; policy while &lt;em&gt;Auth type: NONE&lt;/em&gt; blocks public access to the lambda's URL. &lt;/p&gt;
&lt;h3&gt;
  
  
  Header double-up
&lt;/h3&gt;

&lt;p&gt;AWS adds &lt;code&gt;access-control-allow-origin&lt;/code&gt; and &lt;code&gt;access-control-allow-credentials&lt;/code&gt; CORS headers regardless of their presence in the response from the lambda. &lt;/p&gt;

&lt;p&gt;If our lambda returned CORS headers and the Function URL was configured to return CORS as well, the response would become invalid because &lt;code&gt;access-control-allow-origin&lt;/code&gt; and &lt;code&gt;access-control-allow-credentials&lt;/code&gt; would be included twice (see 4 lines at the end):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 200 OK
Date: Thu, 08 Aug 2024 18:53:09 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 22
Connection: keep-alive
Vary: Origin
x-amzn-RequestId: f1d1d6ce-1208-450d-8c61-e5a51f878090
X-Amzn-Trace-Id: root=1-66b51414-3998ea28145bd0d8661c065f;parent=7df2ad2039925c82;sampled=0;lineage=a964c7ca:0
access-control-allow-headers: x-books-authorization,authorization,content-type
access-control-allow-methods: GET, OPTIONS, POST
access-control-allow-origin: https://localhost:8080
access-control-allow-origin: https://localhost:8080
access-control-allow-credentials: true
access-control-allow-credentials: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;An awesome AWS Lambda debugging tool I used to experiment and capture requests and responses: &lt;a href="https://github.com/rimutaka/lambda-debugger-runtime-emulator" rel="noopener noreferrer"&gt;Github&lt;/a&gt; / &lt;a href="https://crates.io/crates/lambda-debugger" rel="noopener noreferrer"&gt;Crates.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS Lambda CORS docs: &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html?icmpid=docs_lambda_help#urls-cors" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html?icmpid=docs_lambda_help#urls-cors&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CORS overview on MDN: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CORS protocol spec: &lt;a href="https://fetch.spec.whatwg.org/#http-cors-protocol" rel="noopener noreferrer"&gt;https://fetch.spec.whatwg.org/#http-cors-protocol&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CORS request headers

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method" rel="noopener noreferrer"&gt;Access-Control-Request-Method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers" rel="noopener noreferrer"&gt;Access-Control-Request-Headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin" rel="noopener noreferrer"&gt;Origin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Required CORS response headers

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods" rel="noopener noreferrer"&gt;Access-Control-Allow-Methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers" rel="noopener noreferrer"&gt;Access-Control-Allow-Headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin" rel="noopener noreferrer"&gt;Access-Control-Allow-Origin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Optional CORS response headers

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age" rel="noopener noreferrer"&gt;Access-Control-Max-Age&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers" rel="noopener noreferrer"&gt;Access-Control-Expose-Headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials" rel="noopener noreferrer"&gt;Access-Control-Allow-Credentials&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>lambda</category>
      <category>aws</category>
      <category>http</category>
    </item>
    <item>
      <title>Passwordless login to Postgres from VSCode SQLTools extension using IDENT/PEER method</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 31 Jan 2024 19:16:29 +0000</pubDate>
      <link>https://forem.com/rimutaka/passwordless-login-to-postgres-from-vscode-sqltools-extension-using-identpeer-method-434l</link>
      <guid>https://forem.com/rimutaka/passwordless-login-to-postgres-from-vscode-sqltools-extension-using-identpeer-method-434l</guid>
      <description>&lt;p&gt;This post explains how to connect to PostgreSQL using the current Linux user account with &lt;em&gt;IDENT/PEER&lt;/em&gt; authentication method.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Postgres version: 16&lt;/li&gt;
&lt;li&gt;SQLTools extension version: 0.28.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This may apply to other Postgres and SQLTools versions. Replace the version number in the paths and URLs as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  About IDENT/PEER Authentication methods
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;IDENT/PEER&lt;/em&gt; authentication method maps the currently logged-in Linux user name to the Postgres user name without a need for storing the password in Postgres or VSCode configuration files.&lt;/p&gt;

&lt;p&gt;With &lt;em&gt;IDENT/PEER&lt;/em&gt; auth, instead of receiving the login and password from the user, Postgres gets the OS user name of the caller and maps it to the user name stored in its &lt;em&gt;pg_authid&lt;/em&gt; system catalog.&lt;br&gt;
This method is enabled by default in &lt;em&gt;/etc/postgresql/16/main/pg_hba.conf&lt;/em&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# Database administrative login by Unix domain socket
&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;   &lt;span class="n"&gt;all&lt;/span&gt;             &lt;span class="n"&gt;postgres&lt;/span&gt;                                &lt;span class="n"&gt;peer&lt;/span&gt;

&lt;span class="c"&gt;# TYPE  DATABASE        USER            ADDRESS                 METHOD
&lt;/span&gt;
&lt;span class="c"&gt;# "local" is for Unix domain socket connections only
&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;   &lt;span class="n"&gt;all&lt;/span&gt;             &lt;span class="n"&gt;all&lt;/span&gt;                                     &lt;span class="n"&gt;peer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the &lt;em&gt;PEER&lt;/em&gt; auth to work we need to connect via &lt;em&gt;sockets&lt;/em&gt;, not TCP/IP.&lt;br&gt;
A socket is a logical endpoint for communication that bypasses the networking hardware.&lt;/p&gt;

&lt;p&gt;Postgres has a default socket at either: &lt;em&gt;/run/postgresql&lt;/em&gt; or &lt;em&gt;/var/run/postgresql&lt;/em&gt;.&lt;br&gt;
The exact location is configured in &lt;em&gt;/etc/postgresql/16/main/postgresql.conf&lt;/em&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;unix_socket_directories&lt;/span&gt; = &lt;span class="s1"&gt;'/var/run/postgresql'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/auth-pg-hba-conf.html" rel="noopener noreferrer"&gt;Postgres Client auth methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/auth-peer.html" rel="noopener noreferrer"&gt;PEER auth method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unix.stackexchange.com/a/193665" rel="noopener noreferrer"&gt;What is a socket&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/catalog-pg-authid.html" rel="noopener noreferrer"&gt;How Postgres stores user names&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/auth-username-maps.html" rel="noopener noreferrer"&gt;Mapping OS user names to Postgres roles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install Postgress with default settings
&lt;/h2&gt;

&lt;p&gt;Skip this section if you already have Postgres installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# add the PostgreSQL 16 repository&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" &amp;gt; /etc/apt/sources.list.d/pgdg.list'&lt;/span&gt;

&lt;span class="c"&gt;# import the repository signing key&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://www.postgresql.org/media/keys/ACCC4CF8.asc | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/trusted.gpg.d/postgresql.gpg

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update

&lt;span class="c"&gt;# install PostgreSQL 16&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;postgresql-16

&lt;span class="c"&gt;# start and enable PostgreSQL service&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start postgresql
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;postgresql

&lt;span class="c"&gt;# check the installed version&lt;/span&gt;
psql &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should be able to connect to Postgres via &lt;em&gt;psql&lt;/em&gt; utility with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres psql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/download/linux/ubuntu/" rel="noopener noreferrer"&gt;Postgres installation on Ubuntu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/app-psql.html" rel="noopener noreferrer"&gt;About pslq&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Add your current Linux user to Postgres
&lt;/h2&gt;

&lt;p&gt;Postgres creates a new Linux user called &lt;em&gt;postgres&lt;/em&gt; to match the default superuser name in Postgres.&lt;br&gt;
For &lt;em&gt;IDENT/PEER&lt;/em&gt; authentication to work you need to create a Postgres user matching your OS username:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres createuser &lt;span class="nt"&gt;--superuser&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;your_linux_username]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should be able to connect to Postgres without a need for &lt;code&gt;sudo -u postgres&lt;/code&gt; from then on. For example, running &lt;code&gt;psql -d postgres&lt;/code&gt; command should print something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql (16.1 (Ubuntu 16.1-1.pgdg23.04+1))
Type "help" for help.

postgres=#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;postgres=#&lt;/code&gt; prompt means you are a superuser. Otherwise, it would be &lt;code&gt;postgres=&amp;gt;&lt;/code&gt; for less privileged Postgres users.&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/current/app-createuser.html" rel="noopener noreferrer"&gt;&lt;em&gt;createuser&lt;/em&gt; utility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://phoenixnap.com/kb/postgres-create-user" rel="noopener noreferrer"&gt;multiple ways of creating Postgres users&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connecting VS Code to Postgres
&lt;/h2&gt;

&lt;p&gt;Install SQLTools extension for VSCode if you have not done so.&lt;/p&gt;

&lt;p&gt;Do not attempt to use the UI provided by the extension to configure PEER access.&lt;br&gt;
Open &lt;em&gt;settings.json&lt;/em&gt; and add the following section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"sqltools.connections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PGSQL PEER TEST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/run/postgresql"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"driver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PostgreSQL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgres"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;name&lt;/em&gt; - whatever you name it&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;server&lt;/em&gt; - leave as in the example for a default installation on Linux&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;driver&lt;/em&gt; - leave as is&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;database&lt;/em&gt; - use your DB name, if you have one, but "postgres" DB is a good option for the test because it's guaranteed to be there&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save the settings and you should see a new connection option appear in the SQLTools sidebar.&lt;/p&gt;

&lt;p&gt;Try connecting to the server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;click on &lt;em&gt;Connect&lt;/em&gt; icon against the connection you've just added&lt;/li&gt;
&lt;li&gt;you should get a popup saying &lt;em&gt;The extension wants to sign in using SQLTools Driver Credentials&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;click on &lt;em&gt;Allow&lt;/em&gt; and enter the password for the current Linux user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The extension saves the password via VSCode &lt;em&gt;SecretStorage API&lt;/em&gt; which is protected by the OS keyring. It is much more secure than storing it withing VSCode settings.&lt;/p&gt;

&lt;p&gt;You can choose to save it permanently or only for the duration of the VSCode session by clicking on the &lt;em&gt;Key&lt;/em&gt; icon in the password dialog. You will have to enter the password again after restarting VSCode if you choose not to save it.&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=mtxr.sqltools" rel="noopener noreferrer"&gt;SQLTools extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vscode-sqltools.mteixeira.dev/en/drivers/postgre-sql#11-specific-options" rel="noopener noreferrer"&gt;SQLTools Postgres config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/api/references/vscode-api#SecretStorage" rel="noopener noreferrer"&gt;SecretStorage API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/vscode-discussions/discussions/748" rel="noopener noreferrer"&gt;Secret storage discussion on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mtxr/vscode-sqltools/issues/667" rel="noopener noreferrer"&gt;GitHub issue discussing this topic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vscode</category>
      <category>postgres</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Chrome extension example with Rust and WASM</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 08 Jan 2024 01:42:47 +0000</pubDate>
      <link>https://forem.com/rimutaka/chrome-extension-with-rust-and-wasm-by-example-5cbh</link>
      <guid>https://forem.com/rimutaka/chrome-extension-with-rust-and-wasm-by-example-5cbh</guid>
      <description>&lt;p&gt;This post explains the inner workings of a Chrome extension built with Rust and compiled into WASM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target audience:&lt;/strong&gt; Rust programmers interested in building cross-browser extensions with WASM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;About the extension:&lt;/strong&gt; &lt;a href="https://github.com/rimutaka/spotify-playlist-builder" rel="noopener noreferrer"&gt;Github&lt;/a&gt;, &lt;a href="https://chromewebstore.google.com/detail/spotify-playlist-builder/kmbnbjbfpnchgmmkbeidpllpamcahljn" rel="noopener noreferrer"&gt;Chrome Webstore&lt;/a&gt;, &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/spotify-playlist-builder-addon/" rel="noopener noreferrer"&gt;Mozilla Webstore&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you will learn:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;toolchain &lt;/li&gt;
&lt;li&gt;architecture of the extension&lt;/li&gt;
&lt;li&gt;how WASM, background and content scripts communicate with each other&lt;/li&gt;
&lt;li&gt;intercepting session tokens to impersonate the user&lt;/li&gt;
&lt;li&gt;debugging&lt;/li&gt;
&lt;li&gt;making it work for Chrome and Firefox&lt;/li&gt;
&lt;li&gt;listing in Google and Mozilla addon stores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will get the best value for your time if you &lt;a href="https://github.com/rimutaka/spotify-playlist-builder" rel="noopener noreferrer"&gt;look at the source code&lt;/a&gt; and read this post side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Toolchain
&lt;/h2&gt;

&lt;p&gt;The main tool to build, test and publish WASM: &lt;a href="https://rustwasm.github.io/docs/wasm-pack/" rel="noopener noreferrer"&gt;https://rustwasm.github.io/docs/wasm-pack/&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cargo install wasm-pack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See &lt;a href="https://github.com/rimutaka/spotify-playlist-builder/blob/master/build.sh" rel="noopener noreferrer"&gt;build.sh&lt;/a&gt; for an example of &lt;a href="https://rustwasm.github.io/docs/wasm-pack/commands/build.html" rel="noopener noreferrer"&gt;build command&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;cargo&lt;/em&gt; installs &lt;a href="https://rustwasm.github.io/wasm-bindgen/" rel="noopener noreferrer"&gt;wasm-bindgen&lt;/a&gt; on the first compilation to facilitate high-level interactions between wasm modules and JavaScript.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://rustwasm.github.io/wasm-bindgen/" rel="noopener noreferrer"&gt;https://rustwasm.github.io/wasm-bindgen/&lt;/a&gt; - Rust/WASM book with examples, explanations and details references. Read about:

&lt;ul&gt;
&lt;li&gt;Hello, World!&lt;/li&gt;
&lt;li&gt;console.log&lt;/li&gt;
&lt;li&gt;Without a bundler&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://rustwasm.github.io/wasm-bindgen/api/web_sys/index.html" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://rustwasm.github.io/wasm-bindgen/api/web_sys/index.html" rel="noopener noreferrer"&gt;https://rustwasm.github.io/wasm-bindgen/api/web_sys/index.html&lt;/a&gt; - a Rust wrapper around &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API" rel="noopener noreferrer"&gt;Web APIs&lt;/a&gt;. APIs of interest:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API" rel="noopener noreferrer"&gt;Service Workers API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noopener noreferrer"&gt;Fetch API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://rustwasm.github.io/wasm-bindgen/api/js_sys/index.html" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://rustwasm.github.io/wasm-bindgen/api/js_sys/index.html" rel="noopener noreferrer"&gt;https://rustwasm.github.io/wasm-bindgen/api/js_sys/index.html&lt;/a&gt; - a Rust wrapper around JS types and objects&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture of the extension
&lt;/h2&gt;

&lt;p&gt;This extension consists of several logical modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;manifest&lt;/em&gt; - the definition of the extension for the browser&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;background script&lt;/em&gt; - a collection of JS scripts and WASM code that run in a separate browser process regardless of the current web page&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;content script&lt;/em&gt; - the part of the extension that runs inside the current web page, but it is not used in this extension&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;popup&lt;/em&gt; - a window displayed by the browser when the user clicks on the extension button in the toolbar &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Folders
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwazelz30uc6xkdsnw775.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwazelz30uc6xkdsnw775.png" alt="screenshot of solution folders" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;extension&lt;/em&gt;: the code of the extension as it is loaded into the browser, including JS and WASM&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;media&lt;/em&gt;: various media files for publishing the extension, not essential&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;wasm_mod&lt;/em&gt;: Rust code to be compiled into WASM &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;wasm_mod/samples&lt;/em&gt;: Spotify request/response captures, not essential&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Manifest V3
&lt;/h3&gt;

&lt;p&gt;We have to use separate manifest V3 files for different browsers because of incompatible features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;extension/manifest_cr.json&lt;/em&gt;: Chrome version&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;extension/manifest_ff.json&lt;/em&gt;: Firefox version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Browser-specific manifests are renamed into &lt;em&gt;manifest.json&lt;/em&gt; by &lt;em&gt;build.sh&lt;/em&gt; as explained in &lt;em&gt;Debugging&lt;/em&gt; and &lt;em&gt;Packaging&lt;/em&gt; sections of this document.&lt;/p&gt;

&lt;p&gt;List of manifest properties to pay attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;action/show_matches&lt;/em&gt; - when the browser should make the extension button active&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;action/default_popup&lt;/em&gt; - what should happen when the user clicks on the extension button&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;background/service_worker&lt;/em&gt; - the name of the script to run as a background service worker&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;content_security_policy&lt;/em&gt; - declares what the extension can do, e.g. load scripts or WASM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other properties are either self-explanatory or not as important.&lt;/p&gt;

&lt;p&gt;Manifest V3 docs: &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json" rel="noopener noreferrer"&gt;MDN&lt;/a&gt; / &lt;a href="https://developer.chrome.com/docs/extensions/reference" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Background script
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/rimutaka/spotify-playlist-builder/blob/master/extension/js/background.js" rel="noopener noreferrer"&gt;extension/js/background.js&lt;/a&gt; acts as a Service Worker. The name "background script" is historical and can be used interchangeably with "service worker".&lt;/p&gt;

&lt;p&gt;What &lt;code&gt;background.js&lt;/code&gt; does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loads and initializes WASM module&lt;/li&gt;
&lt;li&gt;listens to messages from WASM and the popup page (&lt;a href="https://github.com/rimutaka/spotify-playlist-builder/blob/master/extension/js/popup.html" rel="noopener noreferrer"&gt;extension/js/popup.html&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;sends error messages to the popup page (&lt;a href="https://github.com/rimutaka/spotify-playlist-builder/blob/master/extension/js/popup.js" rel="noopener noreferrer"&gt;extension/js/popup.js&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;captures session token (&lt;code&gt;captureSessionToken()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;fetches user details (&lt;code&gt;fetchUserDetails()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;extracts the current playlist ID from the active tab URL (&lt;code&gt;getPlaylistIdFromCurrentTabUrl()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;calls WASM functions in response to user actions (&lt;code&gt;add_random_tracks()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;controls the extension icon in the toolbar (&lt;code&gt;toggleToolbarBadge()&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;background.js&lt;/code&gt; is loaded when the browser is started and performs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WASM module initialisation&lt;/li&gt;
&lt;li&gt;adding listeners for messaging and token capture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once running, &lt;code&gt;background.js&lt;/code&gt; and its WASM part continue to run independently of the page it was started from. A service worker will continue running even if the user navigates elsewhere or closes the tab that started it because the &lt;a href="https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle" rel="noopener noreferrer"&gt;Service Worker lifecycle&lt;/a&gt; is separate from that of the document.&lt;/p&gt;

&lt;p&gt;More on service workers: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;MDN&lt;/a&gt; / &lt;a href="https://developer.chrome.com/docs/extensions/develop/concepts/service-workers" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Popup page
&lt;/h3&gt;

&lt;p&gt;The manifest file instructs the browser to open a popup window when the extension is activated by the user, e.g. with a click on the extension icon in the toolbar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqzbb7srm64gc7gselpc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqzbb7srm64gc7gselpc.png" alt="popup activation" width="757" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This line of the manifest defines what popup window to open when the user clicks on the toolbar button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"default_popup": "js/popup.html"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The popup lives only while it is being displayed. It cannot host any long-running processes and it does not retain the state from one activation to the other. All &lt;code&gt;DOMContentLoaded&lt;/code&gt; events fire every time it is activated.&lt;/p&gt;

&lt;p&gt;What &lt;code&gt;popup.js&lt;/code&gt; does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;attaches event handlers to its buttons and links&lt;/li&gt;
&lt;li&gt;handles &lt;em&gt;on-click&lt;/em&gt; for links because browsers do not open link URLs from popups (see &lt;code&gt;chrome.tabs.create()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;listens to messages from &lt;code&gt;background.js&lt;/code&gt; and WASM (see &lt;code&gt;chrome.runtime.onMessage.addListener()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;displays an activity log from the messages it receives from WASM and &lt;em&gt;background.js&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any inline JS in &lt;em&gt;popup.html&lt;/em&gt; is ignored. Any JS code or event handlers have to be loaded externally with &lt;code&gt;&amp;lt;script type="module" src="popup.js"&amp;gt;&lt;/code&gt; because only &lt;em&gt;src&lt;/em&gt; scripts are allowed by &lt;code&gt;content_security_policy/extension_pages&lt;/code&gt; entry of the manifest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content script
&lt;/h3&gt;

&lt;p&gt;Content scripts interact with the UI and DOM. They run in the same context as the page and have access to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window" rel="noopener noreferrer"&gt;Window&lt;/a&gt; object as their main browser context.&lt;/p&gt;

&lt;p&gt;This extension does not interact with the Spotify tab and does not need a content script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Messaging between the scripts
&lt;/h2&gt;

&lt;p&gt;Communication between different scripts (background, popup, WASM) is done via asynchronous message passing. A script A sends a message to a shared pool hoping that the intended recipient is listening and can understand the message. This means that if there are multiple listeners, all of them get the message notification. The sender does not get notifications for the messages it sends out.&lt;/p&gt;

&lt;p&gt;It is possible to invoke functions from a different script within the same extension, but the script will run in the context of the caller. For example, &lt;code&gt;popup.js&lt;/code&gt; can call a &lt;code&gt;background.js&lt;/code&gt; function when the user clicks on a button inside the popup. The invocation will work in the context of the popup and die as soon as the popup is closed.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;code&gt;background.js&lt;/code&gt; is a long-running process. It listens to messages sent to it from other scripts. E.g. &lt;code&gt;chrome.runtime.onMessage.addListener()&lt;/code&gt; function checks the message payload and acts depending on the message contents.&lt;/p&gt;

&lt;p&gt;This extension relies on messaging, not direct invocation.&lt;/p&gt;

&lt;p&gt;Key messaging concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;messages are objects&lt;/li&gt;
&lt;li&gt;the only metadata attached to the message is the sender's details&lt;/li&gt;
&lt;li&gt;structure the message to include any additional metadata, e.g. the type of the payload&lt;/li&gt;
&lt;li&gt;always catch &lt;code&gt;chrome.runtime.sendMessage()&lt;/code&gt; errors because there is no guarantee of delivery and an unhandled error will fail your entire script&lt;/li&gt;
&lt;li&gt;an error is always raised if you send a message and there is no active listener, e.g. if you expect a popup to listen, but the user closed it&lt;/li&gt;
&lt;li&gt;message senders cannot receive their own messages, so if you send and listen within the same script, there will be no message notification to self, only to other listeners&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More on message passing between different scripts: &lt;a href="https://developer.chrome.com/docs/extensions/develop/concepts/messaging" rel="noopener noreferrer"&gt;https://developer.chrome.com/docs/extensions/develop/concepts/messaging&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Messaging examples
&lt;/h3&gt;

&lt;h4&gt;
  
  
  From background.js to popup.js
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;background.js&lt;/code&gt; sends error messages out to anyone who listens. It handles non-delivery errors by ignoring them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;popup.js&lt;/code&gt; listens and displays them if &lt;code&gt;popup.html&lt;/code&gt; is open. If the popup is not open, there is no listener and the sender gets an error. These errors should be handled for the rest of the sender's script to work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chrome.runtime.sendMessage("Already running. Restart the browser if stuck on this message.").then(onSuccess, onError);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where both &lt;code&gt;onSuccess&lt;/code&gt; and &lt;code&gt;onError&lt;/code&gt; do nothing to prevent the error from bubbling up to the top.&lt;/p&gt;

&lt;h4&gt;
  
  
  From popup.js to background.js
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;popup.js&lt;/code&gt; sends a message out when the user clicks &lt;em&gt;Add tracks&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;background.js&lt;/code&gt; listens and invokes WASM to handle the user request.&lt;/p&gt;

&lt;p&gt;The popup script could call a function in the background script to invoke WASM or even call WASM directly, but the popup lives only if open. Also, it wouldn't have access to the token stored in the context of the long-running &lt;code&gt;background.js&lt;/code&gt; process.&lt;/p&gt;

&lt;p&gt;So, we have a long-running background script that lives independently of the tabs or the popup. When a message from the popup arrives, &lt;em&gt;background.js&lt;/em&gt; calls WASM and continues running even if the caller no longer exists.&lt;/p&gt;

&lt;h4&gt;
  
  
  Sending messages from WASM to JS scripts
&lt;/h4&gt;

&lt;p&gt;WASM sends out messages via &lt;code&gt;report_progress()&lt;/code&gt; function located in &lt;code&gt;wasm_mod/src/progress.js&lt;/code&gt; script. That function is imported into &lt;code&gt;wasm_mod/src/lib.rs&lt;/code&gt; as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[wasm_bindgen(module = "/src/progress.js")]
extern "C" {
    pub fn report_progress(msg: &amp;amp;str);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and is called from other Rust functions as a native Rust function.&lt;/p&gt;

&lt;p&gt;The progress reporting from WASM to the popup delivers messages in near-real-time while the WASM process continues running.&lt;/p&gt;

&lt;h2&gt;
  
  
  WASM
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;wasm_mod&lt;/em&gt; folder contains the WASM part of the extension.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inside Cargo.toml
&lt;/h3&gt;

&lt;p&gt;Cargo file: &lt;a href="https://github.com/rimutaka/spotify-playlist-builder/blob/master/wasm_mod/Cargo.toml" rel="noopener noreferrer"&gt;wasm_mod/Cargo.toml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;crate-type = ["cdylib", "rlib"]&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;cdylib&lt;/em&gt;: used to create a dynamic system library, e.g. .so or .dll, but for WebAssembly target it creates a *.wasm file without a start function&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;rlib&lt;/em&gt;: optional, used to create an intermediate "Rust library" for unit testing with &lt;code&gt;wasm-pack test&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More on targets: &lt;a href="https://doc.rust-lang.org/reference/linkage.html" rel="noopener noreferrer"&gt;https://doc.rust-lang.org/reference/linkage.html&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependencies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;wasm-bindgen&lt;/em&gt;: contains the runtime support for &lt;code&gt;#[wasm_bindgen]&lt;/code&gt; attribute, &lt;em&gt;JsValue&lt;/em&gt; interface and other JS bindings (&lt;a href="https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/index.html" rel="noopener noreferrer"&gt;crate docs&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;js-sys&lt;/em&gt;: bindings to JS types, e.g. Array, Date and Promise (&lt;a href="https://rustwasm.github.io/wasm-bindgen/api/js_sys/index.html" rel="noopener noreferrer"&gt;crate docs&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;web-sys&lt;/em&gt;: raw API bindings for Web APIs, 1:1, e.g. the browser Window object is web_sys::Window with all the methods, properties and events available from JS (&lt;a href="https://rustwasm.github.io/wasm-bindgen/api/web_sys/index.html" rel="noopener noreferrer"&gt;crate docs&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember to add WebAPI classes and interfaces as &lt;em&gt;web-sys&lt;/em&gt; features in your Cargo.toml. E.g. if you want to use &lt;em&gt;Window&lt;/em&gt; class in Rust code it has to be added to &lt;em&gt;web-sys&lt;/em&gt; features first.&lt;/p&gt;

&lt;p&gt;More about how Rust binds to browser and JS APIs: &lt;a href="https://rustwasm.github.io/wasm-pack/book/tutorials/npm-browser-packages/template-deep-dive/cargo-toml.html" rel="noopener noreferrer"&gt;https://rustwasm.github.io/wasm-pack/book/tutorials/npm-browser-packages/template-deep-dive/cargo-toml.html&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling WASM from JS example
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;background.js&lt;/em&gt; makes a call to &lt;code&gt;hello_wasm()&lt;/code&gt; from &lt;em&gt;lib.rs&lt;/em&gt; that logs a greeting in the browser console for demo purposes.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;hello_wasm()&lt;/code&gt; sequence diagram
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fej2b2t9vpm09skavu6q8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fej2b2t9vpm09skavu6q8.png" alt="hello_wasm() call stack" width="800" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  How &lt;code&gt;console.log()&lt;/code&gt; is called from Rust
&lt;/h4&gt;

&lt;p&gt;To log something into the browser console our Rust code has to access the logging function of WebAPI provided by the browser.&lt;br&gt;
&lt;em&gt;lib.rs&lt;/em&gt; imports WebAPI &lt;code&gt;console.log()&lt;/code&gt; function and makes it available in Rust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace=console)]
    fn log(s: &amp;amp;str);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The low-level binding is done by &lt;em&gt;web-sys&lt;/em&gt; and &lt;em&gt;js-sys&lt;/em&gt; crates with the help of &lt;em&gt;wasm-bindgen&lt;/em&gt; and &lt;em&gt;wasm-pack&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  How Rust's &lt;code&gt;hello_wasm()&lt;/code&gt; is called from JS
&lt;/h4&gt;

&lt;p&gt;To call a Rust/WASM function from JS we need to export it with &lt;code&gt;#[wasm_bindgen]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;lib.rs&lt;/em&gt; exports &lt;code&gt;hello_wasm()&lt;/code&gt; Rust function and makes it available to &lt;code&gt;background.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[wasm_bindgen]
pub fn hello_wasm() {
    log("Hello from WASM!");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;wasm-bindgen&lt;/em&gt; and &lt;em&gt;wasm-pack&lt;/em&gt; generate &lt;em&gt;wasm_mod.js&lt;/em&gt; file every time we run the build. That file is the glue between WASM and JS. Some of its high-level features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;initializes the WASM module&lt;/li&gt;
&lt;li&gt;exports &lt;code&gt;wasm.hello_wasm()&lt;/code&gt; function for &lt;em&gt;background.js&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;type and param checking&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  How WASM is initialized
&lt;/h4&gt;

&lt;p&gt;Every time the browser activates the extension, it runs the following code from &lt;em&gt;background.js&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import initWasmModule, { hello_wasm } from './wasm/wasm_mod.js';
(async () =&amp;gt; {
    await initWasmModule();
    hello_wasm(); 
})();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;wasm-bindgen&lt;/em&gt; and &lt;em&gt;wasm-pack&lt;/em&gt; generate the initialization routine and place it in &lt;em&gt;wasm_mod.js&lt;/em&gt; file as the default export. &lt;/p&gt;

&lt;h3&gt;
  
  
  lib.rs
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;lib.rs&lt;/em&gt; is the top-level file for our WASM module. It exposes Rust functions and binds to JS interfaces.&lt;/p&gt;

&lt;h4&gt;
  
  
  Main entry point
&lt;/h4&gt;

&lt;p&gt;The main entry point that does the work of adding tracks to the current playlist is &lt;code&gt;pub async fn add_random_tracks(...)&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Browser context
&lt;/h4&gt;

&lt;p&gt;The code will need access to some global functions that are only available through an instance of a browser context or runtime. The two main classes that serve this purpose are &lt;code&gt;WorkerGlobalScope&lt;/code&gt; and &lt;code&gt;Window&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The important difference between the two is that: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WorkerGlobalScope&lt;/code&gt; is available to Service Workers (background scripts running in their own process independent of a web page)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Window&lt;/code&gt; is available to content scripts running in the same process as the web page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both classes are very similar, but have slightly different sets of properties and methods.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;lib.rs&lt;/em&gt; obtains a reference to the right browser runtime inside &lt;code&gt;async fn get_runtime()&lt;/code&gt; and passes it to other functions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;get_runtime()&lt;/code&gt; attempts to get a reference to &lt;code&gt;WorkerGlobalScope&lt;/code&gt; first. If that fails it tries to get a reference to &lt;code&gt;Window&lt;/code&gt; object. One works in Chrome and the other in Firefox.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reporting progress back to the UI
&lt;/h4&gt;

&lt;p&gt;Our WASM code may take minutes to run. It reports its progress and failures back to the UI via browser messaging API.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pub fn report_progress(msg: &amp;amp;str)&lt;/code&gt; in &lt;em&gt;lib.rs&lt;/em&gt; is a proxy for &lt;code&gt;report_progress()&lt;/code&gt; in &lt;em&gt;progress.js&lt;/em&gt;.&lt;br&gt;
It is called from various locations in our Rust code to send progress and error messages to &lt;code&gt;popup.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To make a custom JS function available to Rust code we created &lt;em&gt;wasm_mod/src/progress.js&lt;/em&gt; with the following export:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function report_progress(msg) {
  chrome.runtime.sendMessage(msg).then(handleResponse, handleError);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and matched it by creating this import in &lt;em&gt;lib.rs&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[wasm_bindgen(module = "/src/progress.js")]
extern "C" {
    pub fn report_progress(msg: &amp;amp;str);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;wasm-bindgen&lt;/em&gt; and &lt;em&gt;wasm-pack&lt;/em&gt; generate necessary bindings and copy &lt;em&gt;progress.js&lt;/em&gt; to &lt;em&gt;extension/js/wasm/snippets/wasm_mod-bc4ca452742d1bb1/src/progress.js&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  other .rs files
&lt;/h3&gt;

&lt;p&gt;The rest of .rs files contain vanilla Rust that can be compiled into WASM.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;client.rs&lt;/em&gt; - a single high-level function that brings everything together&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;api_wrappers.rs&lt;/em&gt; - a collection of wrappers for Spotify API, called from client.rs&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;constants.rs&lt;/em&gt; - shared constants, utility functions&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;models.rs&lt;/em&gt; - Rust structures for Spotify requests and responses&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Token capture
&lt;/h2&gt;

&lt;p&gt;The extension needs some kind of credentials to communicate with Spotify on behalf of the user. One way of achieving this without asking the user is to capture the current session token. For that to work, the user has to navigate to a Spotify web page and log in there.&lt;/p&gt;

&lt;p&gt;A frequent Spotify user would already be logged in, because Spotify maintains an active session on all *.spotify.com pages.&lt;/p&gt;

&lt;p&gt;This extension captures all request headers sent to &lt;code&gt;https://api-partner.spotify.com/pathfinder/v1/query&lt;/code&gt; endpoint, including the tokens. The headers are stored in local variables and are copied into requests made by the extension to mimic the Spotify app.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;captureSessionToken()&lt;/code&gt; function from &lt;em&gt;background.js&lt;/em&gt; does the header extraction when &lt;em&gt;onBeforeSendHeaders&lt;/em&gt; event is triggered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chrome.webRequest.onBeforeSednHeaders.addListener(captureSessionToken, { urls: ['https://api-partner.spotify.com/pathfinder/v1/query*'] }, ["requestHeaders"])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;onBeforeSendHeaders&lt;/em&gt; listener reads, but does not modify the headers on &lt;code&gt;https://api-partner.spotify.com/pathfinder/v1/query*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;*&lt;/code&gt; at the end of the URL is necessary for the pattern to work.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;["requestHeaders"]&lt;/code&gt; param instructs the browser to include all the headers in the request details object passed onto &lt;em&gt;captureSessionToken&lt;/em&gt; handler. Only the URL and a few common headers are included If that param is omitted.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;host_permissions": ["*://*.spotify.com/*"]&lt;/code&gt; in &lt;em&gt;manifest.json&lt;/em&gt; is required for &lt;em&gt;onBeforeSendHeaders&lt;/em&gt; to work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All extracted headers are stored in &lt;code&gt;headers&lt;/code&gt; variable inside &lt;em&gt;background.js&lt;/em&gt;. The tokens are stored in &lt;code&gt;auth&lt;/code&gt; and &lt;code&gt;token&lt;/code&gt; vars. None of the headers or tokens are persisted in storage.&lt;/p&gt;

&lt;p&gt;The tokens are passed onto WASM as function parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[wasm_bindgen]
pub async fn add_random_tracks(
    auth_header_value: &amp;amp;str,
    token_header_value: &amp;amp;str,
    playlist_id: &amp;amp;str,
    user_uri: &amp;amp;str,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Debugging
&lt;/h2&gt;

&lt;p&gt;Extensions can be loaded from source code in Firefox and Chrome for testing and debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firefox
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;run &lt;code&gt;. build.sh&lt;/code&gt; to build the WASM code&lt;/li&gt;
&lt;li&gt;go to &lt;em&gt;about:debugging#/runtime/this-firefox&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;click on &lt;em&gt;Load temporary Add-on&lt;/em&gt; button&lt;/li&gt;
&lt;li&gt;Firefox opens a file selector popup asking to select &lt;em&gt;manifest.json&lt;/em&gt; file. Remember to rename &lt;em&gt;manifest_ff.json&lt;/em&gt; into &lt;em&gt;manifest.json&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the extension was loaded correctly, its details should appear inside the &lt;em&gt;Temporary Extensions&lt;/em&gt; section on the same page.&lt;/p&gt;

&lt;p&gt;If you keep losing this obscure &lt;em&gt;about:debugging#/runtime/this-firefox&lt;/em&gt; URL, there is an alternative way of getting to the debugging page in Firefox:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;click on &lt;em&gt;extensions&lt;/em&gt; icon in the toolbar&lt;/li&gt;
&lt;li&gt;click on &lt;em&gt;Manage extensions&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;click on &lt;em&gt;settings&lt;/em&gt; gear-like icon at the top right of the page&lt;/li&gt;
&lt;li&gt;click on &lt;em&gt;Debug Add-ons&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Chrome
&lt;/h3&gt;

&lt;p&gt;The Chrome process is very similar to the one in Firefox.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;run &lt;code&gt;. build.sh&lt;/code&gt; to build the WASM code&lt;/li&gt;
&lt;li&gt;go to &lt;em&gt;chrome://extensions/&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Turn on &lt;em&gt;Developer mode&lt;/em&gt; toggle at the top-right corner of the page&lt;/li&gt;
&lt;li&gt;Cick on &lt;em&gt;Load unpacked&lt;/em&gt; and select the folder with &lt;em&gt;manifest.json&lt;/em&gt;. Remember to rename &lt;em&gt;manifest_cr.json&lt;/em&gt; into &lt;em&gt;manifest.json&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the extension was loaded correctly, its details should appear in the list of extensions on the same page. See &lt;a href="https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked" rel="noopener noreferrer"&gt;Chrome docs&lt;/a&gt; for more info.&lt;/p&gt;

&lt;p&gt;If you forget what the direct URL is (&lt;em&gt;chrome://extensions/&lt;/em&gt;), there is an alternative way of getting to the debugging page in Chrome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;click on &lt;code&gt;...&lt;/code&gt; icon in the Chrome toolbar to open the Chrome options menu&lt;/li&gt;
&lt;li&gt;click on &lt;em&gt;Extensions / Manage Extensions&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Chrome will open chrome://extensions/ page&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Making changes and reloading
&lt;/h3&gt;

&lt;p&gt;Changes to code located inside &lt;em&gt;wasm_mod/src&lt;/em&gt; folder require running &lt;em&gt;build.sh&lt;/em&gt; script to rebuild the WASM code. &lt;/p&gt;

&lt;p&gt;Changes to JS, CSS or asset files inside &lt;em&gt;extension&lt;/em&gt; folder do not require a new build and are picked up by the browser when you reload the extension.&lt;/p&gt;

&lt;p&gt;Click on &lt;em&gt;Reload&lt;/em&gt; icon for the extension to load the latest changes.&lt;/p&gt;

&lt;p&gt;For example, changing &lt;em&gt;lib.rs&lt;/em&gt; or &lt;em&gt;progress.js&lt;/em&gt; requires a rebuild and reload. Changing &lt;em&gt;popup.js&lt;/em&gt; only requires a reload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viewing logs
&lt;/h3&gt;

&lt;p&gt;Different modules log messages into different consoles. If you are not seeing your log message, you are probably looking at the wrong console.&lt;/p&gt;

&lt;p&gt;Content scripts output log messages (e.g. via &lt;code&gt;console.log()&lt;/code&gt;) to the same log as the web page. View them in &lt;em&gt;DevTools / Console&lt;/em&gt; tab for the web page.&lt;/p&gt;

&lt;p&gt;Background scripts, including WASM, send messages to a separate console log. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;in Chrome&lt;/em&gt;: click on &lt;em&gt;Inspect views&lt;/em&gt; link in the extension details panel (e.g. &lt;em&gt;Inspect views service worker (Inactive)&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8acr7cu77gxqscxx3omu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8acr7cu77gxqscxx3omu.png" alt="chrome devtools" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;in Firefox&lt;/em&gt;: click on &lt;em&gt;Inspect&lt;/em&gt; button in the extension details panel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwd6h7fx1049zpyczh2as.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwd6h7fx1049zpyczh2as.png" alt="firefox devtools" width="800" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you follow the steps above, Firefox should open a new DevTools window with Console, Network and other tabs.&lt;/p&gt;

&lt;p&gt;Popup windows (e.g. &lt;em&gt;popup.html&lt;/em&gt;) in Firefox log messages into the same console as the background scripts. &lt;/p&gt;

&lt;p&gt;The popup window in Chrome also logs into the same console as the background script, but you have to enable it with a separate step.&lt;/p&gt;

&lt;p&gt;Right-click on the open popup and select &lt;em&gt;Inspect&lt;/em&gt;. It will open a new DevTools window with both background and popup logs. Closing the popup window kills the DevTools window as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4aycjot3m7xscb937c2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4aycjot3m7xscb937c2b.png" alt="popup devtools" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Network requests and responses from &lt;em&gt;background.js&lt;/em&gt; and WASM appear in the background DevTools window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-browser compatibility
&lt;/h2&gt;

&lt;p&gt;Most of the extension code works in both, Firefox and Chrome. There are a few small differences that have to be kept separate: &lt;em&gt;manifest&lt;/em&gt;, &lt;em&gt;global context&lt;/em&gt; and &lt;em&gt;host_permissions&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  manifest.json
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;background.js&lt;/code&gt; lives under different manifest property names:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firefox: &lt;em&gt;background/scripts&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Chrome: &lt;em&gt;background/service_worker&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Firefox complains about &lt;code&gt;minimum_chrome_version&lt;/code&gt;, &lt;code&gt;offline_enabled&lt;/code&gt; and &lt;code&gt;show_matches&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Chrome rejects &lt;code&gt;browser_specific_settings&lt;/code&gt; required by Firefox.&lt;/p&gt;

&lt;p&gt;See the full list of manifest differences between Firefox and Chrome with this CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git diff --no-index --word-diff extension/manifest_cr.json extension/manifest_ff.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This project is configured to have two manifests in separate files: &lt;em&gt;manifest_cr.json&lt;/em&gt; and &lt;em&gt;manifest_ff.json&lt;/em&gt;. Rename one of them manually into &lt;em&gt;manifest.json&lt;/em&gt; for local debugging and then revert to the browser-specific file when finished. &lt;em&gt;build.sh&lt;/em&gt; script renames them to &lt;em&gt;manifest.json&lt;/em&gt; on the fly during packaging.&lt;/p&gt;

&lt;p&gt;If a package has no &lt;em&gt;manifest.json&lt;/em&gt; it is not recognized as an extension by the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global context
&lt;/h3&gt;

&lt;p&gt;Firefox and Chrome use different global context class names to access methods such as &lt;code&gt;fetch()&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WorkerGlobalScope&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Window&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both classes are part of the standardized WebAPI. The difference is in how they are used in content and background scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chrome&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;web_sys::window()&lt;/code&gt; function returns &lt;em&gt;Some(Window)&lt;/em&gt; for content scripts and &lt;em&gt;None&lt;/em&gt; for Service Workers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;js_sys::global().dyn_into::&amp;lt;WorkerGlobalScope&amp;gt;()&lt;/code&gt; returns &lt;em&gt;Ok(WorkerGlobalScope)&lt;/em&gt; for Service Workers and &lt;em&gt;Err&lt;/em&gt; for content scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Firefox&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;web_sys::window()&lt;/code&gt; function returns &lt;em&gt;Some(Window)&lt;/em&gt; for both, context and Service Worker scripts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;js_sys::global().dyn_into::&amp;lt;WorkerGlobalScope&amp;gt;()&lt;/code&gt; always returns &lt;em&gt;None&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It looks like Firefox is not compliant with the standard or even MDN, but maybe I missed something and got this part wrong.&lt;/p&gt;

&lt;p&gt;See &lt;code&gt;get_runtime()&lt;/code&gt; function in &lt;a href="https://github.com/rimutaka/spotify-playlist-builder/blob/master/wasm_mod/src/lib.rs" rel="noopener noreferrer"&gt;lib.rs&lt;/a&gt; for more implementation details. Also, see &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window" rel="noopener noreferrer"&gt;Window&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope" rel="noopener noreferrer"&gt;WorkerGlobalScope&lt;/a&gt; docs on MDN.&lt;/p&gt;

&lt;h3&gt;
  
  
  Host permissions
&lt;/h3&gt;

&lt;p&gt;Both Chrome and Firefox have this entry in the manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"host_permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"*://*.spotify.com/*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format is the same, but Chrome grants this permission on install and Firefox treats it as an optional permission that has to be requested at runtime.&lt;/p&gt;

&lt;p&gt;The extension code handles this discrepancy gracefully at the cost of some complexity. More info:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the implementation of &lt;code&gt;btn_add&lt;/code&gt; &lt;em&gt;click&lt;/em&gt; listener in &lt;a href="https://github.com/rimutaka/spotify-playlist-builder/blob/master/extension/js/popup.js" rel="noopener noreferrer"&gt;popup.js&lt;/a&gt; has detailed comments&lt;/li&gt;
&lt;li&gt;MDN docs: &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/permissions/request" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/permissions/request&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Firefox's strange behavior: &lt;a href="https://stackoverflow.com/questions/47723297/firefox-extension-api-permissions-request-may-only-be-called-from-a-user-input" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/47723297/firefox-extension-api-permissions-request-may-only-be-called-from-a-user-input&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Listing WASM extensions in Google and Mozilla addon stores
&lt;/h2&gt;

&lt;p&gt;There are no special listing requirements for extensions containing WASM code.&lt;/p&gt;

&lt;p&gt;Both Google and Mozilla may request the source code and build instructions for the WASM part, which may delay the review process. This extension is 100% open source and was always approved within 24hrs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Packaging
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;build.sh&lt;/em&gt; script packages the extension into &lt;em&gt;chrome.zip&lt;/em&gt; and &lt;em&gt;firefox.zip&lt;/em&gt; files. The packaging steps include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building the WASM code with &lt;em&gt;wasm-pack&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;removing unnecessary files&lt;/li&gt;
&lt;li&gt;renaming browser-specific manifest files to &lt;em&gt;manifest.json&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;zipping up &lt;em&gt;extension&lt;/em&gt; folder into &lt;em&gt;chrome.zip&lt;/em&gt; and &lt;em&gt;firefox.zip&lt;/em&gt; with the right manifest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;wasm-pack&lt;/em&gt; copies .js files from &lt;em&gt;wasm_mod/src&lt;/em&gt; folder to &lt;em&gt;extension/js/wasm/snippets/wasm_mod/src&lt;/em&gt;  if they are used for binding to Rust. E.g. &lt;em&gt;wasm_mod/src/progress.js&lt;/em&gt; is copied to &lt;em&gt;extension/js/wasm/snippets/wasm_mod-bc4ca452742d1bb1/src/progress.js&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;See detailed packaging instructions for &lt;a href="https://extensionworkshop.com/documentation/publish/package-your-extension/" rel="noopener noreferrer"&gt;Firefox&lt;/a&gt; and &lt;a href="https://developer.chrome.com/docs/extensions/how-to/distribute" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt; for more info.&lt;/p&gt;

&lt;h3&gt;
  
  
  Listing
&lt;/h3&gt;

&lt;p&gt;The listing form has no WASM-specific questions or options. Make sure you do not have any unused permissions in the manifest and give clear explanations why you need the permissions you request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7d8uame3txkq6qkvv05f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7d8uame3txkq6qkvv05f.png" alt="chrome permissions" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Useful links
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Firefox&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Example: &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/spotify-playlist-builder-addon/" rel="noopener noreferrer"&gt;https://addons.mozilla.org/en-US/firefox/addon/spotify-playlist-builder-addon/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sign up here: &lt;a href="https://addons.mozilla.org/en-US/developers/" rel="noopener noreferrer"&gt;https://addons.mozilla.org/en-US/developers/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;List your extensions: &lt;a href="https://addons.mozilla.org/en-US/developers/addons" rel="noopener noreferrer"&gt;https://addons.mozilla.org/en-US/developers/addons&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Uploading listing images may only be available after the extension is approved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Chrome&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Example: &lt;a href="https://chromewebstore.google.com/detail/spotify-playlist-builder/kmbnbjbfpnchgmmkbeidpllpamcahljn" rel="noopener noreferrer"&gt;https://chromewebstore.google.com/detail/spotify-playlist-builder/kmbnbjbfpnchgmmkbeidpllpamcahljn&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sign up and list your extensions: &lt;a href="https://chrome.google.com/webstore/devconsole" rel="noopener noreferrer"&gt;https://chrome.google.com/webstore/devconsole&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Listing process
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sign up, create a new listing, describe the extension and upload the &lt;em&gt;.zip&lt;/em&gt; for review&lt;/li&gt;
&lt;li&gt;Check any errors and warnings generated by the store website after uploading the &lt;em&gt;.zip&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Approval time for both stores is about 24 hours, from personal experience.&lt;/li&gt;
&lt;li&gt;Any change in the code or the listing requires a store review before it is published&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both stores allow listing extensions without them appearing in the public directory while users with the extension URL can still access it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Rust's lazy_static! usage benchmarks and code deep dive</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Sat, 02 Apr 2022 08:44:30 +0000</pubDate>
      <link>https://forem.com/rimutaka/rusts-lazystatic-usage-benchmarks-and-code-deep-dive-1bic</link>
      <guid>https://forem.com/rimutaka/rusts-lazystatic-usage-benchmarks-and-code-deep-dive-1bic</guid>
      <description>&lt;p&gt;&lt;a href="https://crates.io/crates/lazy_static" rel="noopener noreferrer"&gt;&lt;code&gt;lazy_static&lt;/code&gt;&lt;/a&gt; is one of the foundational crates in the Rust ecosystem.&lt;br&gt;
It lets us use static variables without an explicit initialization call.&lt;br&gt;
I used it many times without giving its performance implications much thought. Putting it inside some deeply nested loop got me worried if all that lazy-static magic has some hidden cost.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.rs/lazy_static/latest/lazy_static/index.html" rel="noopener noreferrer"&gt;crate's docs&lt;/a&gt; explain the mechanics behind &lt;code&gt;lazy_static!&lt;/code&gt; macro as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Deref implementation uses a hidden static variable that is guarded by an atomic check on each access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds innocuous enough, but I still have questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is there any noticeable performance cost incurred by the &lt;em&gt;atomic check on each access&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;lazy_static&lt;/code&gt; is used in a sub-module, will it be re-initialized on every call to a function from that module?&lt;/li&gt;
&lt;li&gt;Is it any slower than initializing a variable manually and passing it to other functions as a parameter?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without understanding the implementation details of &lt;code&gt;lazy_static&lt;/code&gt; I figured it would be easier to benchmark it than to dig through its &lt;a href="https://github.com/rust-lang-nursery/lazy-static.rs" rel="noopener noreferrer"&gt;source code&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to run
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;grab project's source: &lt;code&gt;git clone https://github.com/rimutaka/empirical.git&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;benchmarks: &lt;code&gt;cargo +nightly bench&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;tests: &lt;code&gt;cargo +nightly test --benches&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cargo +nightly bench

&lt;span class="nb"&gt;test &lt;/span&gt;bad_rust_local           ... bench:      40,608 ns/iter &lt;span class="o"&gt;(&lt;/span&gt;+/- 9,239&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;test &lt;/span&gt;lazy_static_backref      ... bench:          27 ns/iter &lt;span class="o"&gt;(&lt;/span&gt;+/- 1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;test &lt;/span&gt;lazy_static_external_mod ... bench:          27 ns/iter &lt;span class="o"&gt;(&lt;/span&gt;+/- 0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;test &lt;/span&gt;lazy_static_inner        ... bench:          27 ns/iter &lt;span class="o"&gt;(&lt;/span&gt;+/- 1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;test &lt;/span&gt;lazy_static_local        ... bench:          27 ns/iter &lt;span class="o"&gt;(&lt;/span&gt;+/- 5&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;test &lt;/span&gt;lazy_static_reinit       ... bench:          26 ns/iter &lt;span class="o"&gt;(&lt;/span&gt;+/- 1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;test &lt;/span&gt;once_cell_lazy           ... bench:          26 ns/iter &lt;span class="o"&gt;(&lt;/span&gt;+/- 2&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;test &lt;/span&gt;vanilla_rust_local       ... bench:          27 ns/iter &lt;span class="o"&gt;(&lt;/span&gt;+/- 0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The results looked pretty neat. The only outlier was a piece of bad code I put in the benches intentionally to set the baseline.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;code&gt;lazy_static!&lt;/code&gt; is fine, but &lt;a href="https://docs.rs/once_cell/latest/once_cell/" rel="noopener noreferrer"&gt;&lt;code&gt;once_cell&lt;/code&gt;&lt;/a&gt; may be better for new projects.
&lt;/h4&gt;


&lt;h2&gt;
  
  
  Benchmarks in detail
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;bad_rust_local()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This bench does something obviously stupid - it recompiles the regex within the loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;compiled_regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LONG_REGEX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- don't place this inside a loop&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;is_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compiled_regex&lt;/span&gt;&lt;span class="nf"&gt;.is_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEST_EMAIL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;test&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;black_box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_match&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With 40,608 ns/iter it gives us the baseline for the recompilation cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;vanilla_rust_local()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;There was &lt;strong&gt;NO&lt;/strong&gt; noticeable performance cost incurred by the &lt;em&gt;atomic check on each access&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vanilla_rust_local&lt;/code&gt; bench compiled the regex once and took exactly the same 27 ns/iter as the benches using &lt;code&gt;lazy_static&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;compiled_regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LONG_REGEX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- compiled once only&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;is_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compiled_regex&lt;/span&gt;&lt;span class="nf"&gt;.is_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEST_EMAIL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;test&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;black_box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_match&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;lazy_static_external_mod()&lt;/code&gt;, &lt;code&gt;lazy_static_inner()&lt;/code&gt;, &lt;code&gt;lazy_static_local()&lt;/code&gt;, &lt;code&gt;lazy_static_backref()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;These benches relied on &lt;code&gt;lazy_static&lt;/code&gt; with the only difference in where it was declared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;lazy_static_local:&lt;/strong&gt; at the root level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lazy_static_inner:&lt;/strong&gt; at a sub-module level (same file)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lazy_static_external_mod:&lt;/strong&gt; at a module placed in a separate file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lazy_static_backref:&lt;/strong&gt; at the root level, used in a sub-module&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;lazy_static&lt;/code&gt; declarations were identical in all cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;lazy_static!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LONG_REGEX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The placement of &lt;code&gt;lazy_static! { ... }&lt;/code&gt; declaration made no difference:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The static variable was initialized once only&lt;/li&gt;
&lt;li&gt;All these benches took ~27 ns/iter each.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;lazy_static_reinit()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;It is possible to initialize the static variable before or after its first use by calling&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nn"&gt;lazy_static&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;STATIC_VAR_NAME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There was no additional performance cost for calling &lt;code&gt;initialize&lt;/code&gt; for the first time or any number of times after that.&lt;br&gt;
This is inline with the documentation that states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Takes a shared reference to a lazy static and initializes it if it has not been already.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;once_cell&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.rs/once_cell/latest/once_cell/" rel="noopener noreferrer"&gt;&lt;code&gt;once_cell&lt;/code&gt;&lt;/a&gt; is just as elegant as &lt;code&gt;lazy_static!&lt;/code&gt; and is about 20% faster in a &lt;a href="https://github.com/matklad/once_cell/blob/master/examples/bench_vs_lazy_static.rs" rel="noopener noreferrer"&gt;32-thread test&lt;/a&gt;. It performed on the par with &lt;code&gt;lazy_static!&lt;/code&gt; in my basic tests.&lt;/p&gt;

&lt;p&gt;The static declaration is a single line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX_ONCE_CELL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;once_cell&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;once_cell&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LONG_REGEX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the usage of the static variable is exactly the same as with &lt;code&gt;lazy_static!&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;is_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX_ONCE_CELL&lt;/span&gt;&lt;span class="nf"&gt;.is_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEST_EMAIL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nn"&gt;test&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;black_box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_match&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an &lt;a href="https://github.com/rust-lang/rust/issues/74465" rel="noopener noreferrer"&gt;RFC to merge &lt;code&gt;once_cell&lt;/code&gt; into &lt;code&gt;std::lazy&lt;/code&gt;&lt;/a&gt; making it part of the standard library. It may be a more future-proof choice if you are starting a new project.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;lazy_static&lt;/code&gt; alternatives that DO NOT work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Declaring a static variable
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;STATIC_REGEX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LONG_REGEX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;ERROR: calls in statics are limited to constant functions, tuple structs and tuple variants &lt;a href="https://doc.rust-lang.org/error-index.html#E0015" rel="noopener noreferrer"&gt;rustc E0015&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Declaring a const function
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;static_regex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LONG_REGEX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;ERROR: calls in constant functions are limited to constant functions, tuple structs and tuple variants &lt;a href="https://doc.rust-lang.org/error-index.html#E0015" rel="noopener noreferrer"&gt;rustc E0015&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  &lt;code&gt;lazy_static&lt;/code&gt; in depth
&lt;/h1&gt;

&lt;h5&gt;
  
  
  This section goes deep into the source code of &lt;code&gt;lazy_static&lt;/code&gt; to really understand how it works.
&lt;/h5&gt;

&lt;p&gt;The demo code in this project is split into several parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;benches&lt;/strong&gt;: the source for the benchmarks in this post &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;examples/expansion_base.rs&lt;/strong&gt;: a minimal implementation to get expanded code from &lt;code&gt;lazy_static!&lt;/code&gt; macro&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;examples/expanded.rs&lt;/strong&gt;: the expanded code generated by &lt;code&gt;lazy_static!&lt;/code&gt; macro from &lt;em&gt;expansion_base.rs&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/main.rs&lt;/strong&gt;: a self-contained implementation based on the expanded code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your IDE will be unhappy with some parts of the code if you are on &lt;em&gt;stable&lt;/em&gt; channel. Switch to/from &lt;em&gt;nightly&lt;/em&gt; with these commands to get rid of the IDE warnings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustup default nightly
rustup default stable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Macro's code
&lt;/h2&gt;

&lt;p&gt;Running &lt;code&gt;cargo expand --example expansion_base&lt;/code&gt; outputs the code generated for the crate. You can find the full output in &lt;a href="https://github.com/rimutaka/empirical/tree/master/examples/expanded.rs" rel="noopener noreferrer"&gt;examples/expanded.rs&lt;/a&gt; file.&lt;/p&gt;

&lt;p&gt;In short, it looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#![feature(prelude_import)]&lt;/span&gt;
&lt;span class="nd"&gt;#[prelude_import]&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;rust_2021&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nd"&gt;#[macro_use]&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt; &lt;span class="n"&gt;lazy_static&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;lazy_static&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;lazy_static&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nd"&gt;#[allow(missing_copy_implementations)]&lt;/span&gt;
&lt;span class="nd"&gt;#[allow(non_camel_case_types)]&lt;/span&gt;
&lt;span class="nd"&gt;#[allow(dead_code)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;__private_field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nd"&gt;#[doc(hidden)]&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;__private_field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;lazy_static&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;__Deref&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;deref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;#[inline(always)]&lt;/span&gt;
        &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;__static_ref_initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nd"&gt;#[inline(always)]&lt;/span&gt;
        &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;__stability&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;LAZY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;lazy_static&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;lazy_static&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;INIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;LAZY&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__static_ref_initialize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;__stability&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;lazy_static&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;LazyStatic&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;**&lt;/span&gt;&lt;span class="n"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt;&lt;span class="nf"&gt;.is_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"abc"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snippet above depends on some functions provided by &lt;code&gt;lazy_static&lt;/code&gt; and that's where most of the magic happens.&lt;/p&gt;

&lt;p&gt;I distilled it to a simpler version that does not have &lt;code&gt;lazy_static&lt;/code&gt; as a dependency at all. Skim through it and go to the explanations that follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Sync&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Once&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Sync&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Sync&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;CompiledRegex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;__private_field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CompiledRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CompiledRegex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;__private_field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Deref&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;CompiledRegex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;deref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;LAZY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nn"&gt;Once&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;LAZY&lt;/span&gt;&lt;span class="na"&gt;.1&lt;/span&gt;&lt;span class="nf"&gt;.call_once&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;LAZY&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt;&lt;span class="nf"&gt;.set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LONG_REGEX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;LAZY&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nb"&gt;None&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nd"&gt;panic!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"attempted to dereference an uninitialized lazy static. This is a bug"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few key features in the last snippet to pay attention to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;struct Lazy&lt;/code&gt; holds generic &lt;code&gt;struct CompiledRegex&lt;/code&gt; that is instantiated as &lt;code&gt;static COMPILED_REGEX&lt;/code&gt; that actually holds the compiled regex.&lt;/li&gt;
&lt;li&gt;That long chain is unravelled inside &lt;code&gt;impl Deref for CompiledRegex&lt;/code&gt; to give us the compiled regex as a static variable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;std::sync::Once::call_once()&lt;/code&gt; is used to initialize the regex once only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;match *LAZY.0.as_ptr()&lt;/code&gt; gets us the initialized regex from deep inside the chain of structs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the above code is still a bit confusing, look up &lt;a href="https://github.com/rimutaka/empirical/tree/master/src/main.rs" rel="noopener noreferrer"&gt;src/main.rs&lt;/a&gt; for the full version with detailed comments.&lt;/p&gt;

&lt;p&gt;Running the program with &lt;code&gt;cargo run&lt;/code&gt; will execute this demo code from &lt;a href="https://github.com/rimutaka/empirical/tree/master/src/main.rs" rel="noopener noreferrer"&gt;src/main.rs&lt;/a&gt; using the snippet above for the &lt;em&gt;lazy-static&lt;/em&gt; part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Program started"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{TEST_EMAIL} is valid: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt;&lt;span class="nf"&gt;.is_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEST_EMAIL&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{TEST_NOT_EMAIL} is valid: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;COMPILED_REGEX&lt;/span&gt;&lt;span class="nf"&gt;.is_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEST_NOT_EMAIL&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and produce this output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Program started

Derefencing CompiledRegex

CompiledRegex initialized

name@example.com is valid: true

Derefencing CompiledRegex

Hello world! is valid: false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, &lt;code&gt;COMPILED_REGEX&lt;/code&gt; is initialized once only and is dereferenced every time &lt;code&gt;COMPILED_REGEX&lt;/code&gt; variable is used. &lt;/p&gt;

&lt;p&gt;Q.E.D.? :)&lt;/p&gt;

</description>
      <category>rust</category>
      <category>performance</category>
    </item>
    <item>
      <title>193 hours of music for coding and writing</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 31 Jan 2022 23:36:01 +0000</pubDate>
      <link>https://forem.com/rimutaka/193-hours-of-music-for-coding-and-writing-2076</link>
      <guid>https://forem.com/rimutaka/193-hours-of-music-for-coding-and-writing-2076</guid>
      <description>&lt;p&gt;I find it easier to write and code when the background music has no lyrics I can understand. These words are written to some groovy sounds of a trumpet from my &lt;a href="https://open.spotify.com/playlist/3h9rkMXa434AeAIDdA5Dd2" rel="noopener noreferrer"&gt;Coding Time playlist on Spotify&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Duration&lt;/strong&gt;: 193h+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Style&lt;/strong&gt;: eclectic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Includes&lt;/strong&gt;: world, classical, jazz, electronica, instrumental, indie&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excludes&lt;/strong&gt;: rap, heavy or overly emotional sounds, anything with lyrics in English&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What are you favorite focus playlists or albums?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>programming</category>
      <category>writing</category>
    </item>
    <item>
      <title>Goodbye AWS OpenSearch, hello self-hosted ElasticSearch on EC2</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Sun, 30 Jan 2022 23:55:07 +0000</pubDate>
      <link>https://forem.com/rimutaka/goodbye-aws-opensearch-hello-self-hosted-elasticsearch-on-ec2-454d</link>
      <guid>https://forem.com/rimutaka/goodbye-aws-opensearch-hello-self-hosted-elasticsearch-on-ec2-454d</guid>
      <description>&lt;h4&gt;
  
  
  An AWS OpenSearch to EC2-hosted ElasticSearch migration guide
&lt;/h4&gt;

&lt;p&gt;AWS ElasticSearch Service used to be a quick and easy option to add ElasticSearch to a project already hosted on AWS. It was forked into &lt;a href="https://aws.amazon.com/opensearch-service/" rel="noopener noreferrer"&gt;AWS OpenSearch&lt;/a&gt; and is now only &lt;a href="https://aws.amazon.com/blogs/opensource/introducing-opensearch/" rel="noopener noreferrer"&gt;nominally related to ElasticSearch&lt;/a&gt;. That change created a dilemma to stay with this new AWS service or make a move elsewhere to stay with ElasticSearch.&lt;/p&gt;

&lt;p&gt;The future of OpenSearch doesn't look bright. &lt;a href="https://github.com/opensearch-project/OpenSearch" rel="noopener noreferrer"&gt;AWS OpenSearch project on github&lt;/a&gt; has tanked since AWS took over while &lt;a href="https://github.com/elastic/elasticsearch" rel="noopener noreferrer"&gt;ElasticSearch project&lt;/a&gt; is keeping up a steady pace.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5v2woe9ze7l1iejwj9f3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5v2woe9ze7l1iejwj9f3.png" alt="AWS OpenSearch contributions" width="800" height="221"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxq2dhvol2dz7nmvep0bz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxq2dhvol2dz7nmvep0bz.png" alt="Elastic project contributions" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The other consideration against AWS OpenSearch is its high cost compared to other options.&lt;/p&gt;

&lt;p&gt;For example, using a mix of &lt;em&gt;on-demand&lt;/em&gt; and &lt;em&gt;spot&lt;/em&gt; EC2 instances running ElasticSearch can cost just 1/3 of the comparable OpenSearch configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4jycwidmutemvvmzcpuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4jycwidmutemvvmzcpuk.png" alt="AWS OpenSearch cost comparison" width="800" height="629"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Target EC2 configuration
&lt;/h2&gt;

&lt;p&gt;Mixing EC2 on-demand and spot instances is very cost-effective for non-mission-critical projects. If the ES cluster is configured correctly, the worst case scenario of all spot instances going down is degraded performance with no data loss. It is possible only if the shards are allocated so that there is at least one copy in the on-demand part of the cluster. This setting is addressed later in this guide.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxj1petrpenzflsolubac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxj1petrpenzflsolubac.png" alt="ElasticSearch Spot Deployment" width="800" height="631"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The number of nodes can be easily scaled with addition of more &lt;em&gt;master&lt;/em&gt; and &lt;em&gt;data&lt;/em&gt; nodes from the same AMI. Both types can be run as either &lt;em&gt;spot&lt;/em&gt; or &lt;em&gt;on-demand&lt;/em&gt; as long as there are enough on-demand instances to hold at least one copy of every shard.&lt;/p&gt;
&lt;h2&gt;
  
  
  ElasticSearch fear factor
&lt;/h2&gt;

&lt;p&gt;ElasticSearch installation and configuration may look daunting at first. I was definitely intimidate by the JVM and all the "cluster-node-master-whatnot terminology". It turned out to be easy to install and not too hard to configure. I did have to read a lot of their docs to fill in the blanks in their &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html" rel="noopener noreferrer"&gt;official installation guide&lt;/a&gt;. The rest of this post contains the exact steps I had to go through to create my ElasticSearch cluster and migrate all the data from AWS OpenSearch to it.&lt;/p&gt;
&lt;h4&gt;
  
  
  I am not scared of you, ElasticSearch, any more!
&lt;/h4&gt;


&lt;h2&gt;
  
  
  ES Node AMI
&lt;/h2&gt;

&lt;p&gt;The first task is to build an ElasticSearch Node AMI (ES Node AMI) to serve as a blueprint for any node type. The size of the instance is not important at this stage. It will be easy to resize after creation of the AMI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ElasticSearch version&lt;/strong&gt;: 7.16&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS AMI&lt;/strong&gt;: Ubuntu 20.04LTS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: 50GB root + 60GB data GP3 SSD (&lt;a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/sizing-domains.html#bp-storage" rel="noopener noreferrer"&gt;storage sizing guide&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static IP&lt;/strong&gt;: required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misc&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hostname type&lt;/strong&gt;: Resource name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced monitoring&lt;/strong&gt;: Yes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We only need to set up a single machine to create a reusable ES Node AMI. Any differences between ES nodes will be in the configuration applied after the AMI is launched.&lt;/p&gt;

&lt;p&gt;Start by launching a brand new Ubuntu 20.04 instance on EC2 with the above config.&lt;/p&gt;
&lt;h2&gt;
  
  
  ES installation
&lt;/h2&gt;

&lt;p&gt;This guide is based on the &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/deb.html" rel="noopener noreferrer"&gt;official ES install guide for Debian Package&lt;/a&gt;. It was used to build a 3-node production cluster with a single &lt;em&gt;master&lt;/em&gt; on-demand and 2 &lt;em&gt;data&lt;/em&gt; spot instances.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Important note about Shell Scripts
&lt;/h4&gt;

&lt;p&gt;Most scripts in this guide are formatted as blocks and can be copy-pasted as such into the CLI. It is better to &lt;strong&gt;copy-paste them line by line&lt;/strong&gt; because there is no error handling and some steps require manual input.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# update the OS&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get upgrade

&lt;span class="c"&gt;# ES pre-requisites&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;unzip
wget &lt;span class="nt"&gt;-qO&lt;/span&gt; - https://artifacts.elastic.co/GPG-KEY-elasticsearch | &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-key add -
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;apt-transport-https
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb https://artifacts.elastic.co/packages/7.x/apt stable main"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/elastic-7.x.list

&lt;span class="c"&gt;# install ES&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;elasticsearch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ES is now installed, but not enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage configuration
&lt;/h2&gt;

&lt;p&gt;Attach the 2nd disk to the ES data directory to make it portable. If the instance dies or has to be replaced the index data can be salvaged by re-attaching the volume to another ES EC2 instance.&lt;/p&gt;

&lt;p&gt;Mount the 2nd EBS drive to &lt;code&gt;/var/lib/elasticsearch&lt;/code&gt; folder. Make sure the permissions of the mount are set exactly as in the target folder (&lt;code&gt;drwxr-s---  2 elasticsearch elasticsearch&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mkfs &lt;span class="nt"&gt;-t&lt;/span&gt; xfs /dev/nvme1n1
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount /dev/nvme1n1 /var/lib/elasticsearch
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;elasticsearch:elasticsearch /var/lib/elasticsearch
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;2750 /var/lib/elasticsearch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mount the drive on boot by adding it to &lt;em&gt;fstab&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Update &lt;em&gt;fstab&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"UUID=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;blkid | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s1"&gt;'(?&amp;lt;=/dev/nvme1n1: UUID=")[a-z0-9\-]+'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; /var/lib/elasticsearch xfs defaults 0 2"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it is still a valid &lt;em&gt;fstab&lt;/em&gt; file. The instance may fail to boot if its corrupt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;blkid
&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/fstab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;fstab&lt;/em&gt; should look similar to this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@i-0c4ba3e0d781e7f34:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;blkid
/dev/nvme0n1p1: &lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"cloudimg-rootfs"&lt;/span&gt; &lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"436cf32d-5e3d-46ca-b557-f870c8a25794"&lt;/span&gt; &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ext4"&lt;/span&gt; &lt;span class="nv"&gt;PARTUUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"24ca9e81-01"&lt;/span&gt;
/dev/loop0: &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"squashfs"&lt;/span&gt;
/dev/loop1: &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"squashfs"&lt;/span&gt;
/dev/loop2: &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"squashfs"&lt;/span&gt;
/dev/loop3: &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"squashfs"&lt;/span&gt;
/dev/loop4: &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"squashfs"&lt;/span&gt;
/dev/nvme1n1: &lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"b90fdf46-25a9-48a2-a7e6-82986da39306"&lt;/span&gt; &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"xfs"&lt;/span&gt;
ubuntu@i-0c4ba3e0d781e7f34:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/fstab
&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cloudimg-rootfs   /    ext4   defaults,discard    0 1
&lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;b90fdf46-25a9-48a2-a7e6-82986da39306 /var/lib/elasticsearch xfs defaults 0 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reboot to make sure the VM comes back to life and there are no old file handles pointing at the mounting folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;shutdown &lt;span class="nt"&gt;-r&lt;/span&gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Kibana and plugins
&lt;/h2&gt;

&lt;p&gt;Install ES S3 plugin to backup and restore using AWS S3 for snapshot storage. It is needed to restore a backup from AWS OpenSearch. Do not try to restart ES as instructed by the plugin. We'll do that later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/share/elasticsearch/bin/elasticsearch-plugin &lt;span class="nb"&gt;install &lt;/span&gt;repository-s3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Install Kibana:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;kibana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Useful shortcuts and troubleshooting
&lt;/h3&gt;

&lt;p&gt;A &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/deb.html#deb-layout" rel="noopener noreferrer"&gt;detailed overview of the distro layout&lt;/a&gt; can be summarized with a few frequently used commands:&lt;/p&gt;

&lt;h4&gt;
  
  
  Config files
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;ES config: &lt;code&gt;sudo nano /etc/elasticsearch/elasticsearch.yml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Kibana config: &lt;code&gt;sudo nano /etc/kibana/kibana.yml&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Log files
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;ES log: &lt;code&gt;sudo tail -n 100 /var/log/elasticsearch/stm-prod.log&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Kibana log: &lt;code&gt;sudo tail -n 100 /var/log/kibana/kibana.log&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;systemctl log: &lt;code&gt;tail -n 100  /var/log/syslog&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Folders
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;ES tools: &lt;em&gt;/usr/share/elasticsearch/bin&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;ES path.data: &lt;em&gt;/var/lib/elasticsearch&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;ES path.logs: &lt;em&gt;/var/log/elasticsearch&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  CLI &lt;code&gt;curl&lt;/code&gt; calls
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Check ES is running: &lt;code&gt;curl -X GET "localhost:9200/?pretty"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Over HTTPS locally: &lt;code&gt;curl --insecure -X GET "https://localhost:9200/?pretty"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Over HTTPS anywhere: &lt;code&gt;curl -X GET "https://master.es.stackmuncher.com:9200/?pretty"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check Kibana is running: &lt;code&gt;curl --insecure --verbose -L http://localhost:5601/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Delete all Kibana indexes: &lt;code&gt;curl --user $STM_ES_BASIC_AUTH -XDELETE https://master.es.stackmuncher.com:9200/.kibana&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;View all ES indexes: &lt;code&gt;curl --user $STM_ES_BASIC_AUTH https://master.es.stackmuncher.com:9200/_cat/indices&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;ES log&lt;/strong&gt; is placed in the file named after the cluster. Initially it is called &lt;em&gt;elasticsearch.log&lt;/em&gt;. As soon as the cluster is renamed into &lt;em&gt;stm-prod&lt;/em&gt;, the log name changes to &lt;em&gt;stm-prod.log&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;systemctl log&lt;/strong&gt; is useful for troubleshooting failed &lt;code&gt;start&lt;/code&gt; commands. ES start-up errors are likely to be found there. &lt;strong&gt;Kibana always starts OK&lt;/strong&gt; and then places any errors in its own &lt;em&gt;/var/log/kibana/kibana.log&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$STM_ES_BASIC_AUTH&lt;/strong&gt; env variable should be set to &lt;code&gt;elastic:your_elastic_autogenerated_pwd&lt;/code&gt;. The password will be generated at a later step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Networking
&lt;/h2&gt;

&lt;p&gt;ES instances need to be accessible by clients from within the VPC and from outside the VPC. Create a single public access point at &lt;em&gt;master.es.stackmuncher.com&lt;/em&gt; with 600 TTL in Route53 for both access modes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;public hosted zone:  e.g. &lt;code&gt;master.es.stackmuncher.com A 34.232.124.28&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;private hosted zone: e.g. &lt;code&gt;master.es.stackmuncher.com CNAME i-0c4ba3e0d781e7f34.ec2.internal&lt;/code&gt; (replace &lt;em&gt;i-0c4ba3e0d781e7f34.ec2.internal&lt;/em&gt; with the instance ID)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test it within and outside of the VPC with  &lt;code&gt;nslookup master.es.stackmuncher.com&lt;/code&gt;. It should resolve to a private and static IPs respectively.&lt;/p&gt;

&lt;p&gt;AWS Lambda Functions need to have &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html" rel="noopener noreferrer"&gt;access to the VPC of the ES instance&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial config
&lt;/h2&gt;

&lt;p&gt;ES configuration is dependant on the use case. I suggest to get something going using this example and then tailor it to your needs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reference:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Recommended 1-2-3 node cluster configurations: &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/high-availability-cluster-small-clusters.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/high-availability-cluster-small-clusters.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Discovery settings: &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery-settings.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery-settings.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Node settings: &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ES configuration is done in &lt;code&gt;.yml&lt;/code&gt; config files and via &lt;code&gt;PUT _cluster/settings&lt;/code&gt; commands once the cluster is up and running. &lt;/p&gt;

&lt;h2&gt;
  
  
  Basic security config
&lt;/h2&gt;

&lt;p&gt;There are 3 levels of security config in ES. A production set up for migration requires the full monty with &lt;em&gt;basic&lt;/em&gt;, &lt;em&gt;transport&lt;/em&gt; and &lt;em&gt;HTTPs&lt;/em&gt; for ES and Kibana.&lt;/p&gt;

&lt;p&gt;Also, it's a good idea to &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/elasticsearch-keystore.html" rel="noopener noreferrer"&gt;password-protect the keystore&lt;/a&gt;. This can be done after the cluster is fully operational.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reference:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-minimal-setup.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-minimal-setup.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-basic-setup.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-basic-setup.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-basic-setup-https.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-basic-setup-https.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll need 2 sets of certificates: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;self-issued for transport level to enable ES nodes securely talk to each other&lt;/li&gt;
&lt;li&gt;a trusted cert for external requests to ES API and Kibana front-end&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Enable and launch ES:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;elasticsearch.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start elasticsearch.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bootstrap ES security
&lt;/h3&gt;

&lt;p&gt;This step will generate random passwords for ES built-in accounts and &lt;strong&gt;can only be run once&lt;/strong&gt;. Save the login/passwords it outputs into a file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"xpack.security.enabled: true"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"discovery.type: single-node"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"network.host: 0.0.0.0"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart elasticsearch.service
&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;discovery.type: single-node&lt;/em&gt; setting is needed to stop ES accepting other nodes. &lt;em&gt;network.host: 0.0.0.0&lt;/em&gt; will make it listen on all IPs, internal and external.&lt;/p&gt;

&lt;p&gt;Kibana security config reference: &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-minimal-setup.html#add-built-in-users" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-minimal-setup.html#add-built-in-users&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Add the password for &lt;em&gt;kibana_system&lt;/em&gt; account when requested:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'elasticsearch.username: "kibana_system"'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/kibana/kibana.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"server.host: 0.0.0.0"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/kibana/kibana.yml
&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/share/kibana/bin/kibana-keystore add elasticsearch.password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Enable and start Kibana:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;kibana.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start kibana.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for a minute or so and check the logs to make sure Kibana started OK.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check the logs: &lt;code&gt;sudo tail -n 100 /var/log/kibana/kibana.log&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;This request should return some HTML: &lt;code&gt;curl --insecure --verbose -L http://localhost:5601/&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Generate transport-level certificates
&lt;/h3&gt;

&lt;p&gt;Transport-level certificates are self-issued certificates that are trusted by all nodes. They can be used for external communications if all your clients can be forced to trust them. It may be easier to get a LetsEncrypt cert and be trusted everywhere. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reference&lt;/strong&gt;: &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-basic-setup.html#generate-certificates" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/7.16/security-basic-setup.html#generate-certificates&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Generate the certificate authority file: &lt;code&gt;sudo /usr/share/elasticsearch/bin/elasticsearch-certutil ca&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is saved as &lt;em&gt;/usr/share/elasticsearch/elastic-stack-ca.p12&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Generate the certificate: &lt;code&gt;sudo /usr/share/elasticsearch/bin/elasticsearch-certutil cert --ca elastic-stack-ca.p12&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is saved as &lt;em&gt;/usr/share/elasticsearch/elastic-certificates.p12&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the cert to its default location:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /usr/share/elasticsearch/elastic-certificates.p12 /etc/elasticsearch/elastic-certificates.p12
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;440 /etc/elasticsearch/elastic-certificates.p12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enable transport-level certificates
&lt;/h3&gt;

&lt;p&gt;Certificates are generated and enabled once only because all nodes are built from the same AMI and share the certs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate 
xpack.security.transport.ssl.client_authentication: required
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12"&lt;/span&gt;  | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml

&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/share/elasticsearch/bin/elasticsearch-keystore add xpack.security.transport.ssl.keystore.secure_password

&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/share/elasticsearch/bin/elasticsearch-keystore add xpack.security.transport.ssl.truststore.secure_password

&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart elasticsearch.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  HTTPs with LetsEncrypt
&lt;/h3&gt;

&lt;p&gt;Communications between ES and external clients should be secured with a publicly trusted CA. &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;LetsEncrypt&lt;/a&gt; is free, but comes with 2 problems: must be renewed every 3 months and the web server must provide the full chain version of the certificate because some intermediaries there are not commonly trusted.&lt;/p&gt;

&lt;p&gt;Generate a LetsEncrypt wildcard certificate on a local machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Issue the cert: &lt;code&gt;sudo certbot certonly --manual&lt;/code&gt; for &lt;code&gt;*.es.stackmuncher.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Local location: &lt;em&gt;/etc/letsencrypt/live/es.stackmuncher.com&lt;/em&gt; with shortcuts pointing at &lt;em&gt;/etc/letsencrypt/archive/es.stackmuncher.com/&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Copy the certificate files to the ES instance:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;scp &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/dev-ld-useast1.pem  /etc/letsencrypt/live/es.stackmuncher.com/fullchain.pem ubuntu@master.es.stackmuncher.com:/home/ubuntu/fullchain.pem

&lt;span class="nb"&gt;sudo &lt;/span&gt;scp &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/dev-ld-useast1.pem  /etc/letsencrypt/live/es.stackmuncher.com/privkey.pem ubuntu@master.es.stackmuncher.com:/home/ubuntu/privkey.pem

&lt;span class="nb"&gt;sudo &lt;/span&gt;scp &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/dev-ld-useast1.pem  /etc/letsencrypt/live/es.stackmuncher.com/chain.pem ubuntu@master.es.stackmuncher.com:/home/ubuntu/chain.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;scp&lt;/em&gt; can't get to the ultimate destination of the certificates on the ES instance, so they are copied into the home directory on the instance first and then moved locally within the instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mv&lt;/span&gt; ~/fullchain.pem /etc/elasticsearch/fullchain.pem
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;root:elasticsearch /etc/elasticsearch/fullchain.pem
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;440 /etc/elasticsearch/fullchain.pem
&lt;span class="nb"&gt;sudo mv&lt;/span&gt; ~/privkey.pem /etc/elasticsearch/privkey.pem
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;root:elasticsearch /etc/elasticsearch/privkey.pem
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;440 /etc/elasticsearch/privkey.pem
&lt;span class="nb"&gt;sudo mv&lt;/span&gt; ~/chain.pem /etc/elasticsearch/chain.pem
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;root:elasticsearch /etc/elasticsearch/chain.pem
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;440 /etc/elasticsearch/chain.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the certificates to ES config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"xpack.security.http.ssl.enabled: true"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"xpack.security.http.ssl.key: /etc/elasticsearch/privkey.pem"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"xpack.security.http.ssl.certificate: /etc/elasticsearch/fullchain.pem"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"xpack.security.http.ssl.certificate_authorities: /etc/elasticsearch/chain.pem"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart elasticsearch.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test ES access via HTTPs internally and externally by running &lt;code&gt;curl -X GET "https://master.es.stackmuncher.com:9200/?pretty"&lt;/code&gt;. In both cases it should return an error similar to this example, which means ES is working and HTTP SSL is configured correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"root_cause"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"security_exception"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"missing authentication credentials for REST request [/?pretty]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"WWW-Authenticate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Basic realm=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;security&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; charset=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;UTF-8&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Bearer realm=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;security&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"ApiKey"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"security_exception"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"missing authentication credentials for REST request [/?pretty]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"WWW-Authenticate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Basic realm=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;security&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; charset=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;UTF-8&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Bearer realm=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;security&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ApiKey"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure kibana
&lt;/h2&gt;

&lt;p&gt;Detailed Kibana configuration: &lt;a href="https://www.elastic.co/guide/en/kibana/current/settings.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/kibana/current/settings.html&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Copy LetsEncrypt certificates from the ES location to Kibana:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/elasticsearch/fullchain.pem /etc/kibana/fullchain.crt
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;root:kibana /etc/kibana/fullchain.crt
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;440 /etc/kibana/fullchain.crt
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/elasticsearch/privkey.pem /etc/kibana/privkey.key
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;root:kibana /etc/kibana/privkey.key
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;440 /etc/kibana/privkey.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the certificates to Kibana config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"elasticsearch.hosts: https://master.es.stackmuncher.com:9200"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/kibana/kibana.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"server.publicBaseUrl: https://master.es.stackmuncher.com:5601"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/kibana/kibana.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"server.ssl.certificate: /etc/kibana/fullchain.crt"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/kibana/kibana.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"server.ssl.key: /etc/kibana/privkey.key"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/kibana/kibana.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"server.ssl.enabled: true"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/kibana/kibana.yml
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart kibana.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kibana should now be available at &lt;a href="https://master.es.stackmuncher.com:5601/" rel="noopener noreferrer"&gt;https://master.es.stackmuncher.com:5601/&lt;/a&gt;. Login as &lt;em&gt;elastic&lt;/em&gt; user with its auto-generated password.&lt;/p&gt;

&lt;p&gt;If Kibana is not working, troubleshoot with &lt;code&gt;tail -n 100  /var/log/syslog&lt;/code&gt; and &lt;code&gt;sudo tail -n 100 /var/log/kibana/kibana.log&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Misc settings
&lt;/h2&gt;

&lt;p&gt;Add any tweaks specific to the set up. E.g. StackMuncher doesn't use GeoIP and ML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ingest.geoip.downloader.enabled: false
xpack.ml.enabled: false"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart elasticsearch.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Anonymous access to ES
&lt;/h2&gt;

&lt;p&gt;AWS OpenSearch access control is different from how it is commonly done in ES. The easiest migration approach is to retain the anonymous access for now, but restrict the anonymous user privileges.&lt;/p&gt;

&lt;p&gt;Reference: &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/anonymous-access.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/anonymous-access.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a role describing anonymous user rights in Kibana:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /_security/role/stm_apps
{
  "run_as": [ "_es_anonymous_user" ],
  "indices": [
    {
      "names": [ "stm*","dev*","stats*" ],
      "privileges": [ "read","write","create" ]
    }
  ]
}

GET /_security/role/stm_apps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Map built in &lt;em&gt;_es_anonymous_user&lt;/em&gt; to &lt;em&gt;stm_apps&lt;/em&gt; role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"xpack.security.authc:
  anonymous:
    roles: stm_apps"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml

&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart elasticsearch.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Anonymous access tests
&lt;/h3&gt;

&lt;p&gt;This call from any machine with sufficient network access to the VM &lt;strong&gt;should fail&lt;/strong&gt; because &lt;em&gt;_es_anonymous_user&lt;/em&gt; is not allowed to create new indexes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://master.es.stackmuncher.com:9200/dev/_doc"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'{ "foo": "bar" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected error: &lt;em&gt;...action [indices:admin/auto_create] is unauthorized for user [_anonymous] with roles [stm_apps]...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Create &lt;em&gt;dev&lt;/em&gt; index in Kibana by running &lt;code&gt;PUT /dev&lt;/code&gt; and repeat the &lt;em&gt;curl&lt;/em&gt; request - it &lt;strong&gt;should succeed&lt;/strong&gt; with &lt;em&gt;dev&lt;/em&gt; index in place.&lt;br&gt;
Clean up with &lt;code&gt;DELETE /dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This call should always fail for &lt;em&gt;_es_anonymous_user&lt;/em&gt;: &lt;code&gt;curl https://master.es.stackmuncher.com:9200/_cat/indices&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create ES Node AMI
&lt;/h2&gt;

&lt;p&gt;The ES instance is now in a state ready to be reused for creating multiple &lt;em&gt;master&lt;/em&gt; and &lt;em&gt;data&lt;/em&gt; nodes by spinning more instances within the same VPC.&lt;/p&gt;

&lt;p&gt;Optionally, remove comment lines starting with &lt;code&gt;#&lt;/code&gt; from ES config to make it more readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'/^#/d'&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kibana config can also be cleaned up with &lt;code&gt;sudo sed -i '/^#/d' /etc/kibana/kibana.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Save &lt;code&gt;/etc/elasticsearch/elasticsearch.yml&lt;/code&gt; and &lt;code&gt;/etc/kibana/kibana.yml&lt;/code&gt; for reference.&lt;/p&gt;

&lt;p&gt;Create an AMI&lt;/p&gt;

&lt;h1&gt;
  
  
  Launching ES nodes
&lt;/h1&gt;

&lt;p&gt;An instance launched from ES Node AMI starts as a single node due to &lt;code&gt;discovery.type: single-node&lt;/code&gt; setting and lack of cluster-level config. It needs to be re-configured to join a cluster as a &lt;em&gt;master&lt;/em&gt; or a &lt;em&gt;data&lt;/em&gt; node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Master node config
&lt;/h2&gt;

&lt;p&gt;This script will turn a standalone ES node into a master:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop elasticsearch.service
&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'/discovery.type: single-node/d'&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'cluster.name: stm-prod
node.name: es-master
discovery.seed_hosts: ["master.es.stackmuncher.com"]

# comment out one of the two reservation options
# this setting affects shard allocation
node.attr.reservation: persistent
# node.attr.reservation: spot

cluster.initial_master_nodes: ["es-master"]
cluster.routing.allocation.awareness.attributes: reservation'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start elasticsearch.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart kibana.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open Kibana at &lt;a href="https://master.es.stackmuncher.com:5601/" rel="noopener noreferrer"&gt;https://master.es.stackmuncher.com:5601/&lt;/a&gt; and check the cluster state with &lt;code&gt;GET /_cluster/state&lt;/code&gt;. It should be named appropriately and have only one node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cluster_name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stm-prod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cluster_uuid"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"voHWCBEdRO-shwxv0ngiaw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;207&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state_uuid"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"x7vj0CmvSlmmHY4ASeCTcA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"master_node"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"V4EZprKlTleq9DzgIfIm1A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"blocks"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nodes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"V4EZprKlTleq9DzgIfIm1A"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es-master"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ephemeral_id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OWTuL7nIRGedVd19oTM0Hw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"transport_address"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"172.31.7.40:9300"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"xpack.installed"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"transform.node"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reservation"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"persistent"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"roles"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"data_cold"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"data_content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"data_frozen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"data_hot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"data_warm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ingest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"remote_cluster_client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"transform"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;node.attr.reservation: persistent&lt;/code&gt; for instances that are unlikely to go offline unexpectedly (reserved or on-demand instances) and &lt;code&gt;node.attr.reservation: spot&lt;/code&gt; for any kind of spot instances. ES will be configured to allocate shards so that even if all spot instances drop off there will still be enough replicas to recover and re-balance.&lt;/p&gt;

&lt;p&gt;If you are trying to recover an instance and Kibana won't start due to its shard loss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kill Kibana indexes with &lt;code&gt;curl --user $STM_ES_BASIC_AUTH -XDELETE https://master.es.stackmuncher.com:9200/.kibana*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;restart Kibana with &lt;code&gt;sudo systemctl restart kibana.service&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;em&gt;master&lt;/em&gt; node is now ready for the production snapshot restore from AWS OpenSearch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data node config
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html" rel="noopener noreferrer"&gt;data node&lt;/a&gt; performs fewer functions than a &lt;em&gt;master&lt;/em&gt; and has to be configured differently.&lt;/p&gt;

&lt;p&gt;Remove Kibana as it's not needed on data nodes. Also, any prior node info should be deleted for a new node to establish itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop elasticsearch.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable kibana.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop kibana.service
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/kibana/kibana.yml
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/elasticsearch/nodes
&lt;span class="nb"&gt;sudo ls&lt;/span&gt; &lt;span class="nt"&gt;-al&lt;/span&gt; /var/lib/elasticsearch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drwxr-s---  2 elasticsearch elasticsearch    6 Jan 27 08:52 .
drwxr-xr-x 42 root          root          4096 Jan 26 22:05 ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;: modify the node name in this script below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'/discovery.type: single-node/d'&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'cluster.name: stm-prod
node.name: es-data-1
node.roles: [ data ]
discovery.seed_hosts: ["master.es.stackmuncher.com"]

# comment out one of the two reservation options
# this setting affects shard allocation
# node.attr.reservation: persistent
node.attr.reservation: spot'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/elasticsearch/elasticsearch.yml
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start elasticsearch.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open Kibana at &lt;a href="https://master.es.stackmuncher.com:5601/" rel="noopener noreferrer"&gt;https://master.es.stackmuncher.com:5601/&lt;/a&gt; and check cluster state with &lt;code&gt;GET _cat/nodes&lt;/code&gt;. It should have the newly added node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;172.31.9.51 9 65 16 0.28 0.32 0.23 d          - es-data-1
172.31.7.40 8 75  4 0.05 0.07 0.10 cdfhimrstw * es-master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Migrate production data from AWS OpenSearch to EC2 ES
&lt;/h1&gt;

&lt;p&gt;Backup and restore to S3 requires &lt;a href="https://www.elastic.co/guide/en/elasticsearch/plugins/7.16/repository-s3-repository.html" rel="noopener noreferrer"&gt;S3 Repo Plugin&lt;/a&gt;. It is installed on AWS OpenSearch and is part of our ES Node AMI.&lt;/p&gt;

&lt;p&gt;Backup / restore is done via &lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshots-register-repository.html" rel="noopener noreferrer"&gt;snapshot repositories&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Use these snapshot commands to list and recreate a repository (replace IDs as needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /_snapshot
DELETE _snapshot/snap-syd
PUT _snapshot/snap-syd
{"type":"s3","settings":{"bucket":"stm-prod-sdvevr3i65bzgp4zuh6b","endpoint":"s3.amazonaws.com"}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A repository must be registered at both ends (AWS OpenSearch and EC2 ES) for the transfer to work.&lt;/p&gt;

&lt;p&gt;Stop all ES clients to prevent any updates until the new ES cluster is operational.&lt;/p&gt;

&lt;p&gt;Start a backup on AWS OpenSearch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUT /_snapshot/snap-syd/es_20220125_1044?wait_for_completion=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the backup progress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /_snapshot/snap-syd/es_20220125_1044
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start a restore on EC2 ES:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /_snapshot/snap-syd/es_20220125_1044/_restore?wait_for_completion=false
{"indices": "dev_*,search_log,stats,stm_*"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the restore progress and shard allocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET _cat/recovery
GET _cat/shards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All shards should be distributed such that there is at least one copy on &lt;code&gt;persistent&lt;/code&gt; nodes and one copy on &lt;code&gt;spot&lt;/code&gt; nodes with no &lt;code&gt;UNASSIGNED&lt;/code&gt; shards.&lt;/p&gt;

&lt;p&gt;Modify replication settings by adjusting the number of replicas if the number of nodes in the new cluster is different from what it was on AWS OpenSearch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PUT _all/_settings
{
  "index.number_of_replicas": 2,
  "unassigned.node_left.delayed_timeout": "5m"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for all shards to be allocated and &lt;code&gt;GET _cluster/health&lt;/code&gt; to return &lt;em&gt;green&lt;/em&gt; cluster status.&lt;/p&gt;

&lt;p&gt;Change the config in all ES clients to point them at &lt;a href="https://master.es.stackmuncher.com:9200" rel="noopener noreferrer"&gt;https://master.es.stackmuncher.com:9200&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;Congratulations - you are now running your own ElasticSearch cluster.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>aws</category>
      <category>opensource</category>
      <category>elasticsearch</category>
    </item>
    <item>
      <title>How to display multiple blank lines in Markdown</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 17 Nov 2021 00:58:33 +0000</pubDate>
      <link>https://forem.com/rimutaka/how-to-display-multiple-blank-lines-in-markdown-20ee</link>
      <guid>https://forem.com/rimutaka/how-to-display-multiple-blank-lines-in-markdown-20ee</guid>
      <description>&lt;p&gt;Ever wished you could space out your blocks of text in markdown a bit more without using non-markdown tags?&lt;/p&gt;

&lt;p&gt;According to markdown rules multiple empty lines are collapsed into a single empty line or just a start of a new paragraph, &lt;a href="https://github.github.com/gfm/#blank-lines" rel="noopener noreferrer"&gt;depending on the flavour&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*Line 1*





*Line 2*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is collapsed into&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Line 1&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Line 2&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;However, this seemingly identical example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*Line 1*
 

 

 

*Line 2*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;retains its white space:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Line 1&lt;/em&gt;&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Line 2&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Both examples look the same to a naked eye in markdown and have no special markup.&lt;/p&gt;

&lt;h4&gt;
  
  
  The difference in the second example is in an invisible Unicode character &lt;code&gt; &lt;/code&gt; followed by a new line.
&lt;/h4&gt;

&lt;p&gt;It fools the rendering code into adding a visually empty &lt;code&gt;&amp;lt;p&amp;gt; &amp;lt;/p&amp;gt;&lt;/code&gt; tag giving you an extra empty line. You can add as many of them as you need alternating with an empty line.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://www.emptycharacter.com/" rel="noopener noreferrer"&gt;emptycharacter.com&lt;/a&gt; there are 16 types of those "invisible characters". I found that &lt;em&gt;Medium Mathematical Space&lt;/em&gt; (&lt;a href="https://unicode-table.com/en/205F/" rel="noopener noreferrer"&gt;U+205F&lt;/a&gt;) works best here, on GitHub, Reddit or sites produced by &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo static site generator&lt;/a&gt;. It looks like this:&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;code&gt; &lt;/code&gt;
&lt;/h1&gt;




&lt;p&gt;Happy&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;              writing&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;                              !&lt;br&gt;
 &lt;/p&gt;

&lt;p&gt;                                                    :)&lt;/p&gt;

</description>
      <category>writing</category>
      <category>markdown</category>
    </item>
    <item>
      <title>Quick tip: if Rust stopped formatting your code ...</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Sun, 24 Oct 2021 09:06:43 +0000</pubDate>
      <link>https://forem.com/rimutaka/quick-tip-if-rust-stopped-formatting-your-code--7i2</link>
      <guid>https://forem.com/rimutaka/quick-tip-if-rust-stopped-formatting-your-code--7i2</guid>
      <description>&lt;p&gt;Sometimes Rust Analyzer stops formatting my VS Code document and there is no message or explanation why.&lt;/p&gt;

&lt;p&gt;This is just one ugly example - lines 46-53 are all over the place. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5yfx6wmeed3lc5b6o63.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5yfx6wmeed3lc5b6o63.png" alt="My ugly code" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No matter how many times I press &lt;code&gt;Ctrl&lt;/code&gt;+&lt;code&gt;Shift&lt;/code&gt;+&lt;code&gt;I&lt;/code&gt; the IDE would not fix the formatting or tell me what's wrong.&lt;/p&gt;

&lt;p&gt;Turns out there is a &lt;strong&gt;simple way to find out why Rust Analyzer fails to format the document:&lt;/strong&gt; &lt;code&gt;cargo fmt&lt;/code&gt;. It will tell you exactly what's broken.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7o5mdx2uslrgjv3fcwcj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7o5mdx2uslrgjv3fcwcj.png" alt="cargo fmt workaround" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case it was a comment on line 44 that got in the way. The document started formatting again as soon as I removed it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslqnt8gnuz2uhasdcdgb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslqnt8gnuz2uhasdcdgb.png" alt="My tidy code" width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Beware, &lt;code&gt;cargo fmt&lt;/code&gt; will attempt to reformat the entire project. Read more about it in &lt;a href="https://github.com/rust-lang/rustfmt" rel="noopener noreferrer"&gt;https://github.com/rust-lang/rustfmt&lt;/a&gt; or run &lt;code&gt;cargo fmt -- --help&lt;/code&gt; for options.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I went back to several other files that were not formatting properly, ran &lt;code&gt;cargo fmt&lt;/code&gt; on them and quickly fixed the problems. Happy coding!&lt;/p&gt;

</description>
      <category>rust</category>
    </item>
    <item>
      <title>How much can you earn on UpWork before you give up?</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Thu, 16 Sep 2021 20:00:05 +0000</pubDate>
      <link>https://forem.com/rimutaka/how-much-can-you-earn-on-upwork-before-you-give-up-3mha</link>
      <guid>https://forem.com/rimutaka/how-much-can-you-earn-on-upwork-before-you-give-up-3mha</guid>
      <description>&lt;h4&gt;
  
  
  A brief analysis of actual UpWork earnings for software developers
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR; The sooner you give up, the more you earn elsewhere.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;In case you don't know what &lt;a href="https://www.upwork.com" rel="noopener noreferrer"&gt;UpWork&lt;/a&gt; is, it is a leading freelance marketplace. They are a publicly listed company with $338M revenue in 2020. Anyone can post a job there, and most professionals can offer their services at the cost of 20% of their earnings. The platform provides no vetting for employers and minimal vetting for freelancers leaving it to the feedback system to weed out bad players. It is one of the better ones out there.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I used to hire on UpWork for our startup. It was an easy way to get things done on a shoestring budget. The results were a mixed bag. Most of the work was either a complete throw-away or prototype-level quality.&lt;br&gt;
We got what we paid for, but there were a few outliers.&lt;/p&gt;

&lt;h2&gt;
  
  
  High-quality freelancers are outliers
&lt;/h2&gt;

&lt;p&gt;Despite considering bids only from freelancers with excellent feedback and &lt;a href="https://support.upwork.com/hc/en-us/articles/211068358" rel="noopener noreferrer"&gt;90%+ Job Success Score&lt;/a&gt; our hit rate was about 1 in 10 for "acceptable" and 1 in 20 for "great". We were lucky to hire some really good freelancers from remote (to us) places like Moldova, Macedonia and Turkey. They were up and coming, had reasonable rates and appreciated the constant stream of work we were sending them. &lt;/p&gt;

&lt;h2&gt;
  
  
  Paying more doesn't get you more quality
&lt;/h2&gt;

&lt;p&gt;There seemed to be a pretty low ceiling on the quality of the workforce there. Assuming that our job ads were well-written, simply offering more money didn't bring better quality bids. None of the higher-cost freelancers we tried ended up on our regulars list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freelancer's lifetime value
&lt;/h2&gt;

&lt;p&gt;I recently got in touch with 11 out of 13 UpWork freelancers we hired on a regular basis. To my surprise, none of them was still using UpWork or any other freelance marketplace. Only 3 continued with casual contract work mostly through referrals from past clients.&lt;/p&gt;

&lt;p&gt;They all had a different story to tell, but the common theme was that the jobs on UpWork were hard to get, many employers had no idea what they were doing and the pay was below the market rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So how much do freelancers make before they move off the platform?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I looked at the total earnings of 100 freelancers in &lt;a href="https://www.upwork.com/ab/profiles/search/?category_uid=531770282580668418&amp;amp;page=1&amp;amp;q=rust" rel="noopener noreferrer"&gt;search results for Rust&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9b9mzf4gmq42nm2crms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff9b9mzf4gmq42nm2crms.png" alt="UpWork earnings breakdown" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Assuming &lt;a href="https://www.reddit.com/r/Upwork/comments/plwc3u/im_just_tired_of_bidding_for_work_with_no/" rel="noopener noreferrer"&gt;you don't give up before landing that very first job&lt;/a&gt;, the chances are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1/3 quits before earning $1,000&lt;/li&gt;
&lt;li&gt;1/4 quits before earning $10,000&lt;/li&gt;
&lt;li&gt;1/3 quits before reaching $100,000&lt;/li&gt;
&lt;li&gt;the rest stays for a long haul and makes UpWork a significant source of their income&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  High-earner list
&lt;/h2&gt;

&lt;p&gt;It's not all doom and gloom. You can make a decent living in some categories.&lt;/p&gt;

&lt;p&gt;9 out of 100 freelancers in my search for a Rust engineer had over $100k of total earnings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.upwork.com/freelancers/~017778f12a2565f8f4" rel="noopener noreferrer"&gt;$185,288&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.upwork.com/freelancers/~0147f8ce8730c3d633" rel="noopener noreferrer"&gt;$210,023&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.upwork.com/freelancers/~01f24fb7d3fa638cfa" rel="noopener noreferrer"&gt;$277,418&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.upwork.com/freelancers/~010ba56ab4bc5aba48" rel="noopener noreferrer"&gt;$281,584&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.upwork.com/freelancers/~0144b974ba8a3d287f" rel="noopener noreferrer"&gt;$295,363&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.upwork.com/freelancers/~010f4e6ae0c1778aae" rel="noopener noreferrer"&gt;$425,345&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.upwork.com/freelancers/~01aa1b7467b4eb49bc" rel="noopener noreferrer"&gt;$673,153&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.upwork.com/freelancers/~01364b7feb73f1d7ae" rel="noopener noreferrer"&gt;$921,241&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.upwork.com/freelancers/~01d95397aacaef6e88" rel="noopener noreferrer"&gt;$1,000,000&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  $1,000,000 club - do you want to be a member?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.upwork.com/freelancers/~01d95397aacaef6e88" rel="noopener noreferrer"&gt;Evgeniy T. from Catalonia&lt;/a&gt; tops our list with $1M+ of lifetime earnings. We don't know exactly how much he earned, but let's assume it is a million.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z6acar5y3kdduht4ppk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z6acar5y3kdduht4ppk.png" alt="One Million Dollar Club" width="753" height="641"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Earning one million dollars sounds very inspirational. Who doesn't want to be a millionaire! Let's break it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$1,000,000 / 18,076 hrs worked = $55 per hour&lt;/li&gt;
&lt;li&gt;period over 10 years (started in 2011)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$117k per year&lt;/strong&gt; if adjusted for inflation and UpWork fees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we disregard the intangible values such as freedom this type of work brings, &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;$117k per year for someone with 10 years of full-stack experience is a rather low figure in today's world.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same high-earners list doesn't look as impressive on an annual basis, except maybe entry #3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.upwork.com/freelancers/api/v1/freelancer/profile/~017778f12a2565f8f4/details" rel="noopener noreferrer"&gt;$185,288&lt;/a&gt; / 5 years&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.upwork.com/freelancers/api/v1/freelancer/profile/~0147f8ce8730c3d633/details" rel="noopener noreferrer"&gt;$210,023&lt;/a&gt; / 12 years&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.upwork.com/freelancers/api/v1/freelancer/profile/~01f24fb7d3fa638cfa/details" rel="noopener noreferrer"&gt;$277,418&lt;/a&gt; / 2 years&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.upwork.com/freelancers/api/v1/freelancer/profile/~010ba56ab4bc5aba48/details" rel="noopener noreferrer"&gt;$281,584&lt;/a&gt; / 12 years&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.upwork.com/freelancers/api/v1/freelancer/profile/~0144b974ba8a3d287f/details" rel="noopener noreferrer"&gt;$295,363&lt;/a&gt; / 6 years&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.upwork.com/freelancers/api/v1/freelancer/profile/~010f4e6ae0c1778aae/details" rel="noopener noreferrer"&gt;$425,345&lt;/a&gt; / 11 years&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.upwork.com/freelancers/api/v1/freelancer/profile/~01aa1b7467b4eb49bc/details" rel="noopener noreferrer"&gt;$673,153&lt;/a&gt; / 12 years&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.upwork.com/freelancers/api/v1/freelancer/profile/~01364b7feb73f1d7ae/details" rel="noopener noreferrer"&gt;$921,241&lt;/a&gt; / 9 years&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.upwork.com/freelancers/api/v1/freelancer/profile/~01d95397aacaef6e88/details" rel="noopener noreferrer"&gt;$1,000,000&lt;/a&gt; / 10 years &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end, UpWork is no better or worse than any other career path. It does a good job of putting you in front of employers. If you are good and want to be poached, you most likely will be, &lt;a href="https://www.upwork.com/legal#OPTINGOUT" rel="noopener noreferrer"&gt;despite what the Terms and Conditions&lt;/a&gt; say.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hey, are you open to offers of better employment or more contract work?
&lt;/h2&gt;

&lt;p&gt;I am building a &lt;a href="https://stackmuncher.com" rel="noopener noreferrer"&gt;software engineer search engine&lt;/a&gt; for devs like you and me to be discovered by employers looking for our skills. It is a community-first and fully open-source project that takes no commission or charges placement fees. Head to &lt;a href="https://stackmuncher.com/about/developers/" rel="noopener noreferrer"&gt;https://stackmuncher.com/about/developers/&lt;/a&gt; and let interesting and well-paid work find you &lt;/p&gt;

</description>
      <category>career</category>
      <category>beginners</category>
      <category>freelance</category>
    </item>
  </channel>
</rss>
