<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dan&apos;s Land</title><description>Dispatches from Dan&apos;s Land.</description><link>https://dans.land</link><language>en</language><item><title>Dotfiles</title><link>https://dans.land/garden/dotfiles</link><guid isPermaLink="true">https://dans.land/garden/dotfiles</guid><description>Collection of my dotfiles and configuration setup on macOS.</description><pubDate>Sun, 05 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;font&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#font&quot;&gt;Font&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://usgraphics.com/products/berkeley-mono&quot;&gt;https://usgraphics.com/products/berkeley-mono&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;terminal&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#terminal&quot;&gt;Terminal&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First install the Xcode CLI tools:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;xcode-select --install&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Then &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;atuin&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#atuin&quot;&gt;Atuin&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Location:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;~/.config/atuin/config.toml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Config:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;# See: https://docs.atuin.sh/latest/configuration/config/

enter_accept = true
filter_mode_shell_up_key_binding = &quot;session&quot;
invert = true&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;ghostty&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#ghostty&quot;&gt;Ghostty&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://ghostty.org/&quot;&gt;https://ghostty.org/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Config:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# Config: https://ghostty.org/docs/config/reference

auto-update = check

background-opacity = 0.95
background-blur-radius = 20

copy-on-select = clipboard

font-family = TX-02 SemiCondensed
font-size = 15

macos-titlebar-style = tabs
macos-option-as-alt = true
macos-icon = holographic

mouse-hide-while-typing = true

quit-after-last-window-closed = true
quick-terminal-position = bottom

selection-invert-fg-bg = true

theme = light:&quot;Monokai Pro Light Sun&quot;, dark:&quot;Monokai Pro Ristretto&quot;

window-colorspace = display-p3
window-height = 45
window-width = 120
window-padding-x = 10
window-padding-y = 10
window-save-state = always&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;git&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#git&quot;&gt;Git&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dandavison.github.io/delta/&quot;&gt;Delta&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/ssh-commit-signing&quot; data-garden-link=&quot;&quot;&gt;SSH commit signing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Location:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;~/.gitconfig&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Config:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[user]
  name = Daniël Illouz
  email = &amp;lt;ID&amp;gt;+&amp;lt;USERNAME&amp;gt;@users.noreply.github.com
  signingkey = ~/.ssh/id_ed25519_github_danillouz.pub

[gpg]
  format = ssh
[gpg &quot;ssh&quot;]
  allowedSignersFile = ~/.config/git_allowed_signers
[commit]
  gpgsign = true
[tag]
  gpgsign = true

[pull]
  rebase = true
[push]
  default = simple
[rebase]
  autosquash = true

[alias]
  alias = config --get-regexp alias
  contribs = shortlog -sn
  fuckit = !git reset --hard HEAD &amp;amp;&amp;amp; git clean -d -f
  tags = tag

# delta: https://dandavison.github.io/delta/
[core]
  pager = delta
[delta]
  line-numbers = true
  hyperlinks = true
  side-by-side = true
  navigate = true
[interactive]
  diffFilter = delta --color-only
[merge]
    conflictstyle = zdiff3&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;prompt&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#prompt&quot;&gt;Prompt&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://starship.rs/&quot;&gt;https://starship.rs/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Location:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;~/.config/starship.toml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Config:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;# See: https://starship.rs/config

&quot;$schema&quot; = &apos;https://starship.rs/config-schema.json&apos;

format = &quot;&quot;&quot;
$username\
$hostname\
$directory\
$git_branch\
$git_commit\
$git_state\
$git_status\
$docker_context\
$sudo\
$cmd_duration\
$line_break\
$character&quot;&quot;&quot;

[character]
success_symbol = &quot;[&amp;gt;](green)&quot;
error_symbol = &quot;[&amp;gt;](red)&quot;
vimcmd_symbol = &quot;[vim](green)&quot;

[directory]
read_only = &quot; readonly&quot;
fish_style_pwd_dir_length = 1

[git_branch]
symbol = &quot;&quot;

[git_status]
ahead = &quot;↑&quot;
behind = &quot;↓&quot;
diverged = &quot;↕&quot;
renamed = &quot;r&quot;
deleted = &quot;x&quot;

[sudo]
disabled = false
symbol = &quot;sudo &quot;
format = &quot;[$symbol]($style)&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;zsh&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#zsh&quot;&gt;Zsh&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://atuin.sh/&quot;&gt;atuin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zsh-users/zsh-autosuggestions&quot;&gt;zsh-autosuggestions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ajeetdsouza/zoxide&quot;&gt;zoxide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;aliases&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#aliases&quot;&gt;Aliases&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Location:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;~/.zsh_aliases&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Config:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# Configuration files
alias config=&apos;ide -n \
  ~/.codex/config.toml \
  ~/.config/atuin/config.toml \
  ~/.config/starship.toml \
  ~/.gitconfig \
  ~/.vimrc \
  ~/.zprofile \
  ~/.zsh_aliases \
  ~/.zshrc \
&apos;

# Git
alias g=&apos;git&apos;

# Git: inspect
alias gs=&apos;git status -sb&apos;
alias gd=&apos;git diff&apos;
alias gds=&apos;git diff --staged&apos;
alias gsh=&apos;git show&apos;
alias gl=&apos;git log --stat --graph --pretty=format:&quot;%C(green)%d%Creset %C(yellow)%h%Creset %C(magenta)(%cr)%Creset %C(blue)&amp;lt;%cn&amp;gt;%Creset %s&quot;&apos;
alias gl1=&apos;git log --pretty=format:&quot;%C(green)%d%Creset %C(yellow)%h%Creset %C(magenta)(%cd)%Creset %C(blue)&amp;lt;%cn&amp;gt;%Creset %s&quot;&apos;

# Git: stage / unstage / discard
alias ga=&apos;git add&apos;
alias gap=&apos;git add -p&apos;
alias gaa=&apos;git add -A&apos;
alias gusp=&apos;git restore --staged -p&apos; # unstage selected hunks
alias gdiscp=&apos;git restore -p&apos;        # discard unstaged hunks (destructive)

# Git: commit / rewrite
alias gc=&apos;git commit -v&apos;
alias gca=&apos;git commit -v -a&apos;
alias gcam=&apos;git commit -v --amend&apos;
alias gcfx=&apos;git commit --fixup&apos;
alias gunc=&apos;git reset --mixed HEAD~&apos; # uncommit, retaining changes

# Git: branches
alias gb=&apos;git branch -vv&apos;
alias gbd=&apos;git branch -d&apos;
alias gbD=&apos;git branch -D&apos;
alias gco=&apos;git checkout&apos;
alias gcob=&apos;git checkout -b&apos;

# Git: sync / remotes
alias gf=&apos;git fetch --prune&apos;
alias gpl=&apos;git pull&apos;
alias gp=&apos;git push origin HEAD&apos;
alias gpu=&apos;git push -u origin HEAD&apos;
alias gpf=&apos;git push --force-with-lease --force-if-includes origin HEAD&apos;
alias grpo=&apos;git remote prune origin&apos;
alias grso=&apos;git remote show origin&apos;

# Git: rebase
alias grb=&apos;git rebase&apos;
alias grbi=&apos;git rebase -i&apos;
alias grbc=&apos;git rebase --continue&apos;
alias grba=&apos;git rebase --abort&apos;

# Git: stash
alias gst=&apos;git stash&apos;
alias gstp=&apos;git stash push -p&apos;
alias gstls=&apos;git stash list&apos;
alias gstpop=&apos;git stash pop&apos;

# ls
alias ll=&apos;ls -alh&apos;

# Misc
alias cx=&apos;codex&apos;
alias ide=&apos;zed&apos;

up() {
  local failed=()

  echo &quot;\nHomebrew&quot;
  brew update &amp;amp;&amp;amp; brew upgrade || failed+=(homebrew)
  brew cleanup

  if (( ${#failed[@]} )); then
    echo &quot;\n\033[31m✗ Failed: ${failed[*]}\033[0m&quot;
  else
    echo &quot;\n\033[32m✓ All updated\033[0m&quot;
  fi
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h4 id=&quot;zprofile&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#zprofile&quot;&gt;zprofile&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Location:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;~/.zprofile&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Config (Apple Silicon):&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;eval &quot;$(/opt/homebrew/bin/brew shellenv)&quot;

# Restore Keychain-backed SSH identities for Git commit signing.
ssh-add --apple-load-keychain &amp;gt;/dev/null 2&amp;gt;&amp;amp;1&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h4 id=&quot;zshrc&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#zshrc&quot;&gt;zshrc&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Location:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;~/.zshrc&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Config:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;setopt auto_cd

# Enables case-insensitive tab-completion.
# See: https://stackoverflow.com/a/69014927
zstyle &apos;:completion:*&apos; matcher-list &apos;&apos; &apos;m:{a-zA-Z}={A-Za-z}&apos; &apos;r:|=*&apos; &apos;l:|=* r:|=*&apos;
autoload -Uz compinit &amp;amp;&amp;amp; compinit

# Run `alias` to see all aliases.
source ~/.zsh_aliases

# See: https://starship.rs/guide/
eval &quot;$(starship init zsh)&quot;

# See: https://github.com/ajeetdsouza/zoxide/#installation
eval &quot;$(zoxide init zsh)&quot;

# Atuin is the persistent history store,
# and automatically adds itself as an autosuggest strategy (zsh-autosuggestions).
#
# See:
#   - https://docs.atuin.sh/latest/guide/installation/#manual-installation
#   - https://docs.atuin.sh/latest/integrations/#zsh-autosuggestions
#   - https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md
unset HISTFILE
SAVEHIST=0
eval &quot;$(atuin init zsh)&quot;
source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh

export PATH=&quot;$HOME/.local/bin:$PATH&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;vim&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#vim&quot;&gt;Vim&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Location:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;~/.vimrc&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Config:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;syntax on

set number
set smartindent
set textwidth=80
set wrap&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;zed&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#zed&quot;&gt;Zed&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Config:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;agent&quot;: {
    &quot;button&quot;: true,
    &quot;default_model&quot;: {
      &quot;effort&quot;: &quot;high&quot;,
      &quot;enable_thinking&quot;: true,
      &quot;model&quot;: &quot;gpt-5.6&quot;,
      &quot;provider&quot;: &quot;openai-subscribed&quot;,
    },
    &quot;default_width&quot;: 600.0,
    &quot;favorite_models&quot;: [],
    &quot;flexible&quot;: false,
    &quot;max_content_width&quot;: 600.0,
    &quot;model_parameters&quot;: [],
    &quot;play_sound_when_agent_done&quot;: &quot;when_hidden&quot;,
    &quot;tool_permissions&quot;: {
      &quot;tools&quot;: {
        &quot;fetch&quot;: {
          &quot;default&quot;: &quot;allow&quot;,
        },
      },
    },
  },
  &quot;agent_servers&quot;: {
    &quot;codex-acp&quot;: {
      &quot;type&quot;: &quot;registry&quot;
    }
  },
  &quot;base_keymap&quot;: &quot;VSCode&quot;,
  &quot;bottom_dock_layout&quot;: &quot;contained&quot;,
  &quot;buffer_font_family&quot;: &quot;TX-02&quot;,
  &quot;buffer_font_size&quot;: 15,
  &quot;cli_default_open_behavior&quot;: &quot;existing_window&quot;,
  &quot;collaboration_panel&quot;: {
    &quot;button&quot;: false,
  },
  &quot;colorize_brackets&quot;: true,
  &quot;edit_predictions&quot;: {
    &quot;allow_data_collection&quot;: &quot;no&quot;,
    &quot;provider&quot;: &quot;none&quot;,
  },
  &quot;git&quot;: {
    &quot;inline_blame&quot;: {
      &quot;enabled&quot;: true,
    },
  },
  &quot;git_panel&quot;: {
    &quot;group_by&quot;: &quot;staging&quot;,
    &quot;default_width&quot;: 300.0,
    &quot;file_icons&quot;: true,
    &quot;show_count_badge&quot;: true,
    &quot;status_style&quot;: &quot;icon&quot;,
    &quot;tree_view&quot;: true,
  },
  &quot;icon_theme&quot;: {
    &quot;dark&quot;: &quot;Zed (Default)&quot;,
    &quot;light&quot;: &quot;Zed (Default)&quot;,
    &quot;mode&quot;: &quot;light&quot;,
  },
  &quot;minimap&quot;: {
    &quot;show&quot;: &quot;auto&quot;,
    &quot;thumb&quot;: &quot;always&quot;,
  },
  &quot;on_last_window_closed&quot;: &quot;quit_app&quot;,
  &quot;preferred_line_length&quot;: 80,
  &quot;project_panel&quot;: {
    &quot;bold_folder_labels&quot;: true,
    &quot;button&quot;: true,
    &quot;default_width&quot;: 300.0,
    &quot;diagnostic_badges&quot;: true,
    &quot;entry_spacing&quot;: &quot;comfortable&quot;,
    &quot;git_status_indicator&quot;: true,
  },
  &quot;proxy&quot;: &quot;&quot;,
  &quot;search&quot;: {
    &quot;button&quot;: true,
  },
  &quot;session&quot;: {
    &quot;trust_all_worktrees&quot;: true,
  },
  &quot;show_edit_predictions&quot;: false,
  &quot;show_whitespaces&quot;: &quot;all&quot;,
  &quot;soft_wrap&quot;: &quot;prefer_line&quot;,
  &quot;status_bar&quot;: {
    &quot;active_language_button&quot;: true,
    &quot;cursor_position_button&quot;: false,
    &quot;show_active_file&quot;: false,
  },
  &quot;sticky_scroll&quot;: {
    &quot;enabled&quot;: true,
  },
  &quot;tab_bar&quot;: {
    &quot;show&quot;: true,
    &quot;show_tab_bar_buttons&quot;: true,
  },
  &quot;tabs&quot;: {
    &quot;close_position&quot;: &quot;left&quot;,
    &quot;file_icons&quot;: true,
    &quot;git_status&quot;: true,
    &quot;show_diagnostics&quot;: &quot;all&quot;,
  },
  &quot;telemetry&quot;: {
    &quot;diagnostics&quot;: false,
    &quot;metrics&quot;: false,
  },
  &quot;terminal&quot;: {
    &quot;blinking&quot;: &quot;on&quot;,
    &quot;copy_on_select&quot;: true,
    &quot;cursor_shape&quot;: &quot;bar&quot;,
    &quot;font_size&quot;: 15.0,
    &quot;show_count_badge&quot;: true,
    &quot;toolbar&quot;: {
      &quot;breadcrumbs&quot;: false,
    },
  },
  &quot;theme&quot;: {
    &quot;mode&quot;: &quot;system&quot;,
    &quot;light&quot;: &quot;Kohi Latte&quot;,
    &quot;dark&quot;: &quot;Kohi Espresso&quot;
  },
  &quot;title_bar&quot;: {
    &quot;show_branch_name&quot;: true,
    &quot;show_branch_status_icon&quot;: false,
    &quot;show_menus&quot;: false,
    &quot;show_project_items&quot;: true,
    &quot;show_sign_in&quot;: false,
    &quot;show_user_menu&quot;: true,
    &quot;show_user_picture&quot;: false,
  },
  &quot;ui_font_size&quot;: 16,
  &quot;wrap_guides&quot;: [80],
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;themes&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/dotfiles#themes&quot;&gt;Themes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Location:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;~/.config/zed/themes&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;&lt;code&gt;kohi.json&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;$schema&quot;: &quot;https://zed.dev/schema/themes/v0.2.0.json&quot;,
  &quot;name&quot;: &quot;Kohi&quot;,
  &quot;author&quot;: &quot;danillouz&quot;,
  &quot;themes&quot;: [
    {
      &quot;name&quot;: &quot;Kohi Latte&quot;,
      &quot;appearance&quot;: &quot;light&quot;,
      &quot;style&quot;: {
        &quot;accents&quot;: [
          &quot;#d4572b&quot;,
          &quot;#ce4770&quot;,
          &quot;#218871&quot;,
          &quot;#b16803&quot;,
          &quot;#6851a2&quot;,
          &quot;#2473b6&quot;,
          &quot;#d4572b&quot;
        ],
        &quot;border&quot;: &quot;#d7cec7&quot;,
        &quot;border.variant&quot;: &quot;#e6ddd5&quot;,
        &quot;border.focused&quot;: &quot;#d4572b&quot;,
        &quot;border.selected&quot;: &quot;#d4572b8c&quot;,
        &quot;border.transparent&quot;: &quot;#00000000&quot;,
        &quot;border.disabled&quot;: &quot;#eee5de&quot;,
        &quot;elevated_surface.background&quot;: &quot;#fffaf5&quot;,
        &quot;surface.background&quot;: &quot;#f3e9e0&quot;,
        &quot;background&quot;: &quot;#eee5de&quot;,
        &quot;element.background&quot;: &quot;#fffaf5&quot;,
        &quot;element.hover&quot;: &quot;#2c232e0c&quot;,
        &quot;element.active&quot;: &quot;#d4572b26&quot;,
        &quot;element.selected&quot;: &quot;#d4572b30&quot;,
        &quot;element.disabled&quot;: &quot;#f3e9e0&quot;,
        &quot;drop_target.background&quot;: &quot;#d4572b4d&quot;,
        &quot;ghost_element.background&quot;: &quot;#00000000&quot;,
        &quot;ghost_element.hover&quot;: &quot;#2c232e0c&quot;,
        &quot;ghost_element.active&quot;: &quot;#d4572b26&quot;,
        &quot;ghost_element.selected&quot;: &quot;#72696d14&quot;,
        &quot;ghost_element.disabled&quot;: &quot;#f3e9e0&quot;,
        &quot;text&quot;: &quot;#2c232e&quot;,
        &quot;text.muted&quot;: &quot;#92898a&quot;,
        &quot;text.placeholder&quot;: &quot;#beb5b3&quot;,
        &quot;text.disabled&quot;: &quot;#92898a&quot;,
        &quot;text.accent&quot;: &quot;#d4572b&quot;,
        &quot;icon&quot;: &quot;#2c232e&quot;,
        &quot;icon.muted&quot;: &quot;#92898a&quot;,
        &quot;icon.disabled&quot;: &quot;#92898a&quot;,
        &quot;icon.placeholder&quot;: &quot;#beb5b3&quot;,
        &quot;icon.accent&quot;: &quot;#d4572b&quot;,
        &quot;status_bar.background&quot;: &quot;#eee5de&quot;,
        &quot;title_bar.background&quot;: &quot;#eee5de&quot;,
        &quot;title_bar.inactive_background&quot;: &quot;#eee5de&quot;,
        &quot;toolbar.background&quot;: &quot;#fffaf5&quot;,
        &quot;tab_bar.background&quot;: &quot;#f3e9e0&quot;,
        &quot;tab.inactive_background&quot;: &quot;#f3e9e0&quot;,
        &quot;tab.active_background&quot;: &quot;#fffaf5&quot;,
        &quot;search.match_background&quot;: &quot;#b1680340&quot;,
        &quot;search.active_match_background&quot;: &quot;#ce477040&quot;,
        &quot;panel.background&quot;: &quot;#f3e9e0&quot;,
        &quot;panel.focused_border&quot;: &quot;#d4572b&quot;,
        &quot;panel.indent_guide&quot;: &quot;#ded5d0aa&quot;,
        &quot;panel.indent_guide_hover&quot;: &quot;#92898a88&quot;,
        &quot;panel.indent_guide_active&quot;: &quot;#d4572b&quot;,
        &quot;pane.focused_border&quot;: &quot;#d4572b&quot;,
        &quot;pane_group.border&quot;: &quot;#d7cec7&quot;,
        &quot;scrollbar.thumb.background&quot;: &quot;#72696d26&quot;,
        &quot;scrollbar.thumb.hover_background&quot;: &quot;#72696d55&quot;,
        &quot;scrollbar.thumb.border&quot;: &quot;#d7cec7&quot;,
        &quot;scrollbar.track.background&quot;: &quot;#00000000&quot;,
        &quot;scrollbar.track.border&quot;: &quot;#e6ddd5&quot;,
        &quot;editor.foreground&quot;: &quot;#2c232e&quot;,
        &quot;editor.background&quot;: &quot;#fffaf5&quot;,
        &quot;editor.gutter.background&quot;: &quot;#fffaf5&quot;,
        &quot;editor.subheader.background&quot;: &quot;#fffaf5&quot;,
        &quot;editor.active_line.background&quot;: &quot;#d4572b0d&quot;,
        &quot;editor.highlighted_line.background&quot;: &quot;#f3e9e080&quot;,
        &quot;editor.line_number&quot;: &quot;#beb5b3&quot;,
        &quot;editor.active_line_number&quot;: &quot;#d4572b&quot;,
        &quot;editor.hover_line_number&quot;: &quot;#2c232e&quot;,
        &quot;editor.invisible&quot;: &quot;#ded5d0cc&quot;,
        &quot;editor.wrap_guide&quot;: &quot;#ded5d0aa&quot;,
        &quot;editor.active_wrap_guide&quot;: &quot;#92898a88&quot;,
        &quot;editor.indent_guide&quot;: &quot;#ded5d0aa&quot;,
        &quot;editor.indent_guide_active&quot;: &quot;#d4572b&quot;,
        &quot;editor.document_highlight.read_background&quot;: &quot;#2473b624&quot;,
        &quot;editor.document_highlight.write_background&quot;: &quot;#d4572b33&quot;,
        &quot;editor.document_highlight.bracket_background&quot;: &quot;#d4572b33&quot;,
        &quot;terminal.background&quot;: &quot;#fffaf5&quot;,
        &quot;terminal.foreground&quot;: &quot;#2c232e&quot;,
        &quot;terminal.bright_foreground&quot;: &quot;#2c232e&quot;,
        &quot;terminal.dim_foreground&quot;: &quot;#92898a&quot;,
        &quot;terminal.ansi.background&quot;: &quot;#fffaf5&quot;,
        &quot;terminal.ansi.black&quot;: &quot;#d2c9c4&quot;,
        &quot;terminal.ansi.red&quot;: &quot;#ce4770&quot;,
        &quot;terminal.ansi.green&quot;: &quot;#218871&quot;,
        &quot;terminal.ansi.yellow&quot;: &quot;#b16803&quot;,
        &quot;terminal.ansi.blue&quot;: &quot;#d4572b&quot;,
        &quot;terminal.ansi.magenta&quot;: &quot;#6851a2&quot;,
        &quot;terminal.ansi.cyan&quot;: &quot;#2473b6&quot;,
        &quot;terminal.ansi.white&quot;: &quot;#2c232e&quot;,
        &quot;terminal.ansi.bright_black&quot;: &quot;#92898a&quot;,
        &quot;terminal.ansi.bright_red&quot;: &quot;#df547d&quot;,
        &quot;terminal.ansi.bright_green&quot;: &quot;#29967d&quot;,
        &quot;terminal.ansi.bright_yellow&quot;: &quot;#c07609&quot;,
        &quot;terminal.ansi.bright_blue&quot;: &quot;#e36637&quot;,
        &quot;terminal.ansi.bright_magenta&quot;: &quot;#765fb2&quot;,
        &quot;terminal.ansi.bright_cyan&quot;: &quot;#2d82c8&quot;,
        &quot;terminal.ansi.bright_white&quot;: &quot;#2c232e&quot;,
        &quot;terminal.ansi.dim_black&quot;: &quot;#d2c9c4cc&quot;,
        &quot;terminal.ansi.dim_red&quot;: &quot;#ce4770cc&quot;,
        &quot;terminal.ansi.dim_green&quot;: &quot;#218871cc&quot;,
        &quot;terminal.ansi.dim_yellow&quot;: &quot;#b16803cc&quot;,
        &quot;terminal.ansi.dim_blue&quot;: &quot;#d4572bcc&quot;,
        &quot;terminal.ansi.dim_magenta&quot;: &quot;#6851a2cc&quot;,
        &quot;terminal.ansi.dim_cyan&quot;: &quot;#2473b6cc&quot;,
        &quot;terminal.ansi.dim_white&quot;: &quot;#2c232ecc&quot;,
        &quot;link_text.hover&quot;: &quot;#2473b6&quot;,
        &quot;version_control.added&quot;: &quot;#218871&quot;,
        &quot;version_control.modified&quot;: &quot;#b16803&quot;,
        &quot;version_control.word_added&quot;: &quot;#21887133&quot;,
        &quot;version_control.word_deleted&quot;: &quot;#ce477033&quot;,
        &quot;version_control.deleted&quot;: &quot;#ce4770&quot;,
        &quot;conflict&quot;: &quot;#d4572b&quot;,
        &quot;conflict.background&quot;: &quot;#d4572b1f&quot;,
        &quot;conflict.border&quot;: &quot;#d4572b70&quot;,
        &quot;created&quot;: &quot;#218871&quot;,
        &quot;created.background&quot;: &quot;#2188711f&quot;,
        &quot;created.border&quot;: &quot;#21887170&quot;,
        &quot;deleted&quot;: &quot;#ce4770&quot;,
        &quot;deleted.background&quot;: &quot;#ce47701f&quot;,
        &quot;deleted.border&quot;: &quot;#ce477070&quot;,
        &quot;error&quot;: &quot;#ce4770&quot;,
        &quot;error.background&quot;: &quot;#ce47701f&quot;,
        &quot;error.border&quot;: &quot;#ce477070&quot;,
        &quot;hidden&quot;: &quot;#92898a&quot;,
        &quot;hidden.background&quot;: &quot;#92898a1a&quot;,
        &quot;hidden.border&quot;: &quot;#92898a66&quot;,
        &quot;hint&quot;: &quot;#2473b6&quot;,
        &quot;hint.background&quot;: &quot;#2473b61f&quot;,
        &quot;hint.border&quot;: &quot;#2473b670&quot;,
        &quot;ignored&quot;: &quot;#92898a&quot;,
        &quot;ignored.background&quot;: &quot;#92898a1a&quot;,
        &quot;ignored.border&quot;: &quot;#92898a66&quot;,
        &quot;info&quot;: &quot;#2473b6&quot;,
        &quot;info.background&quot;: &quot;#2473b61f&quot;,
        &quot;info.border&quot;: &quot;#2473b670&quot;,
        &quot;modified&quot;: &quot;#b16803&quot;,
        &quot;modified.background&quot;: &quot;#b168031f&quot;,
        &quot;modified.border&quot;: &quot;#b1680370&quot;,
        &quot;predictive&quot;: &quot;#92898a&quot;,
        &quot;predictive.background&quot;: &quot;#92898a1a&quot;,
        &quot;predictive.border&quot;: &quot;#92898a66&quot;,
        &quot;renamed&quot;: &quot;#6851a2&quot;,
        &quot;renamed.background&quot;: &quot;#6851a21f&quot;,
        &quot;renamed.border&quot;: &quot;#6851a270&quot;,
        &quot;success&quot;: &quot;#218871&quot;,
        &quot;success.background&quot;: &quot;#2188711f&quot;,
        &quot;success.border&quot;: &quot;#21887170&quot;,
        &quot;unreachable&quot;: &quot;#92898a&quot;,
        &quot;unreachable.background&quot;: &quot;#92898a1a&quot;,
        &quot;unreachable.border&quot;: &quot;#92898a66&quot;,
        &quot;warning&quot;: &quot;#d4572b&quot;,
        &quot;warning.background&quot;: &quot;#d4572b1f&quot;,
        &quot;warning.border&quot;: &quot;#d4572b70&quot;,
        &quot;players&quot;: [
          {
            &quot;cursor&quot;: &quot;#d4572b&quot;,
            &quot;background&quot;: &quot;#d4572b&quot;,
            &quot;selection&quot;: &quot;#d4572b3d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#ce4770&quot;,
            &quot;background&quot;: &quot;#ce4770&quot;,
            &quot;selection&quot;: &quot;#ce47703d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#218871&quot;,
            &quot;background&quot;: &quot;#218871&quot;,
            &quot;selection&quot;: &quot;#2188713d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#b16803&quot;,
            &quot;background&quot;: &quot;#b16803&quot;,
            &quot;selection&quot;: &quot;#b168033d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#6851a2&quot;,
            &quot;background&quot;: &quot;#6851a2&quot;,
            &quot;selection&quot;: &quot;#6851a23d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#2473b6&quot;,
            &quot;background&quot;: &quot;#2473b6&quot;,
            &quot;selection&quot;: &quot;#2473b63d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#2c232e&quot;,
            &quot;background&quot;: &quot;#2c232e&quot;,
            &quot;selection&quot;: &quot;#2c232e3d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#d4572b&quot;,
            &quot;background&quot;: &quot;#d4572b&quot;,
            &quot;selection&quot;: &quot;#d4572b3d&quot;
          }
        ],
        &quot;syntax&quot;: {
          &quot;attribute&quot;: {
            &quot;color&quot;: &quot;#218871&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;boolean&quot;: {
            &quot;color&quot;: &quot;#6851a2&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;comment&quot;: {
            &quot;color&quot;: &quot;#a59c9c&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;comment.doc&quot;: {
            &quot;color&quot;: &quot;#72696d&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;constant&quot;: {
            &quot;color&quot;: &quot;#6851a2&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;constructor&quot;: {
            &quot;color&quot;: &quot;#2473b6&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;embedded&quot;: {
            &quot;color&quot;: &quot;#2c232e&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;emphasis&quot;: {
            &quot;color&quot;: &quot;#d4572b&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;emphasis.strong&quot;: {
            &quot;color&quot;: &quot;#6851a2&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: 700
          },
          &quot;enum&quot;: {
            &quot;color&quot;: &quot;#2473b6&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;function&quot;: {
            &quot;color&quot;: &quot;#218871&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;hint&quot;: {
            &quot;color&quot;: &quot;#2473b6&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;keyword&quot;: {
            &quot;color&quot;: &quot;#ce4770&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;label&quot;: {
            &quot;color&quot;: &quot;#6851a2&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;link_text&quot;: {
            &quot;color&quot;: &quot;#d4572b&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;link_uri&quot;: {
            &quot;color&quot;: &quot;#2473b6&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;namespace&quot;: {
            &quot;color&quot;: &quot;#2473b6&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;number&quot;: {
            &quot;color&quot;: &quot;#6851a2&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;operator&quot;: {
            &quot;color&quot;: &quot;#ce4770&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;predictive&quot;: {
            &quot;color&quot;: &quot;#92898a&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;preproc&quot;: {
            &quot;color&quot;: &quot;#6851a2&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;primary&quot;: {
            &quot;color&quot;: &quot;#2c232e&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;property&quot;: {
            &quot;color&quot;: &quot;#2c232e&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation&quot;: {
            &quot;color&quot;: &quot;#92898a&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation.bracket&quot;: {
            &quot;color&quot;: &quot;#72696d&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation.delimiter&quot;: {
            &quot;color&quot;: &quot;#72696d&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation.list_marker&quot;: {
            &quot;color&quot;: &quot;#ce4770&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation.markup&quot;: {
            &quot;color&quot;: &quot;#ce4770&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation.special&quot;: {
            &quot;color&quot;: &quot;#df547d&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;selector&quot;: {
            &quot;color&quot;: &quot;#218871&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;selector.pseudo&quot;: {
            &quot;color&quot;: &quot;#2473b6&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;string&quot;: {
            &quot;color&quot;: &quot;#b16803&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;string.escape&quot;: {
            &quot;color&quot;: &quot;#6851a2&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;string.regex&quot;: {
            &quot;color&quot;: &quot;#b16803&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;string.special&quot;: {
            &quot;color&quot;: &quot;#d4572b&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;string.special.symbol&quot;: {
            &quot;color&quot;: &quot;#d4572b&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;tag&quot;: {
            &quot;color&quot;: &quot;#ce4770&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;text.literal&quot;: {
            &quot;color&quot;: &quot;#b16803&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;title&quot;: {
            &quot;color&quot;: &quot;#b16803&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: 600
          },
          &quot;type&quot;: {
            &quot;color&quot;: &quot;#2473b6&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;variable&quot;: {
            &quot;color&quot;: &quot;#2c232e&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;variable.member&quot;: {
            &quot;color&quot;: &quot;#2c232e&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;variable.parameter&quot;: {
            &quot;color&quot;: &quot;#d4572b&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;variable.special&quot;: {
            &quot;color&quot;: &quot;#72696d&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;variant&quot;: {
            &quot;color&quot;: &quot;#218871&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;diff.plus&quot;: {
            &quot;color&quot;: &quot;#218871&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;diff.minus&quot;: {
            &quot;color&quot;: &quot;#ce4770&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          }
        }
      }
    },
    {
      &quot;name&quot;: &quot;Kohi Espresso&quot;,
      &quot;appearance&quot;: &quot;dark&quot;,
      &quot;style&quot;: {
        &quot;accents&quot;: [
          &quot;#f38d70&quot;,
          &quot;#fd6883&quot;,
          &quot;#adda78&quot;,
          &quot;#f9cc6c&quot;,
          &quot;#a8a9eb&quot;,
          &quot;#85dacc&quot;,
          &quot;#f38d70&quot;
        ],
        &quot;border&quot;: &quot;#463a35&quot;,
        &quot;border.variant&quot;: &quot;#342b27&quot;,
        &quot;border.focused&quot;: &quot;#f38d70&quot;,
        &quot;border.selected&quot;: &quot;#f38d708c&quot;,
        &quot;border.transparent&quot;: &quot;#00000000&quot;,
        &quot;border.disabled&quot;: &quot;#332a26&quot;,
        &quot;elevated_surface.background&quot;: &quot;#332a26&quot;,
        &quot;surface.background&quot;: &quot;#27201d&quot;,
        &quot;background&quot;: &quot;#191412&quot;,
        &quot;element.background&quot;: &quot;#332a26&quot;,
        &quot;element.hover&quot;: &quot;#f0e4d012&quot;,
        &quot;element.active&quot;: &quot;#f38d7030&quot;,
        &quot;element.selected&quot;: &quot;#f38d7038&quot;,
        &quot;element.disabled&quot;: &quot;#27201d&quot;,
        &quot;drop_target.background&quot;: &quot;#f38d704d&quot;,
        &quot;ghost_element.background&quot;: &quot;#00000000&quot;,
        &quot;ghost_element.hover&quot;: &quot;#f0e4d012&quot;,
        &quot;ghost_element.active&quot;: &quot;#f38d7030&quot;,
        &quot;ghost_element.selected&quot;: &quot;#f0e4d014&quot;,
        &quot;ghost_element.disabled&quot;: &quot;#27201d&quot;,
        &quot;text&quot;: &quot;#f0e4d0&quot;,
        &quot;text.muted&quot;: &quot;#9a8d82&quot;,
        &quot;text.placeholder&quot;: &quot;#756961&quot;,
        &quot;text.disabled&quot;: &quot;#9a8d82&quot;,
        &quot;text.accent&quot;: &quot;#f38d70&quot;,
        &quot;icon&quot;: &quot;#f0e4d0&quot;,
        &quot;icon.muted&quot;: &quot;#9a8d82&quot;,
        &quot;icon.disabled&quot;: &quot;#9a8d82&quot;,
        &quot;icon.placeholder&quot;: &quot;#756961&quot;,
        &quot;icon.accent&quot;: &quot;#f38d70&quot;,
        &quot;status_bar.background&quot;: &quot;#191412&quot;,
        &quot;title_bar.background&quot;: &quot;#191412&quot;,
        &quot;title_bar.inactive_background&quot;: &quot;#201917&quot;,
        &quot;toolbar.background&quot;: &quot;#201917&quot;,
        &quot;tab_bar.background&quot;: &quot;#191412&quot;,
        &quot;tab.inactive_background&quot;: &quot;#191412&quot;,
        &quot;tab.active_background&quot;: &quot;#201917&quot;,
        &quot;search.match_background&quot;: &quot;#f9cc6c40&quot;,
        &quot;search.active_match_background&quot;: &quot;#fd688340&quot;,
        &quot;panel.background&quot;: &quot;#27201d&quot;,
        &quot;panel.focused_border&quot;: &quot;#f38d70&quot;,
        &quot;panel.indent_guide&quot;: &quot;#51453f99&quot;,
        &quot;panel.indent_guide_hover&quot;: &quot;#9a8d8288&quot;,
        &quot;panel.indent_guide_active&quot;: &quot;#f38d70&quot;,
        &quot;pane.focused_border&quot;: &quot;#f38d70&quot;,
        &quot;pane_group.border&quot;: &quot;#463a35&quot;,
        &quot;scrollbar.thumb.background&quot;: &quot;#9a8d8240&quot;,
        &quot;scrollbar.thumb.hover_background&quot;: &quot;#c3b3a56e&quot;,
        &quot;scrollbar.thumb.border&quot;: &quot;#463a35&quot;,
        &quot;scrollbar.track.background&quot;: &quot;#00000000&quot;,
        &quot;scrollbar.track.border&quot;: &quot;#342b27&quot;,
        &quot;editor.foreground&quot;: &quot;#f0e4d0&quot;,
        &quot;editor.background&quot;: &quot;#201917&quot;,
        &quot;editor.gutter.background&quot;: &quot;#201917&quot;,
        &quot;editor.subheader.background&quot;: &quot;#201917&quot;,
        &quot;editor.active_line.background&quot;: &quot;#f0e4d00c&quot;,
        &quot;editor.highlighted_line.background&quot;: &quot;#332a2680&quot;,
        &quot;editor.line_number&quot;: &quot;#756961&quot;,
        &quot;editor.active_line_number&quot;: &quot;#f38d70&quot;,
        &quot;editor.hover_line_number&quot;: &quot;#f0e4d0&quot;,
        &quot;editor.invisible&quot;: &quot;#51453faa&quot;,
        &quot;editor.wrap_guide&quot;: &quot;#51453f&quot;,
        &quot;editor.active_wrap_guide&quot;: &quot;#9a8d8288&quot;,
        &quot;editor.indent_guide&quot;: &quot;#51453f99&quot;,
        &quot;editor.indent_guide_active&quot;: &quot;#f38d70&quot;,
        &quot;editor.document_highlight.read_background&quot;: &quot;#85dacc24&quot;,
        &quot;editor.document_highlight.write_background&quot;: &quot;#f38d7033&quot;,
        &quot;editor.document_highlight.bracket_background&quot;: &quot;#f38d7033&quot;,
        &quot;terminal.background&quot;: &quot;#201917&quot;,
        &quot;terminal.foreground&quot;: &quot;#f0e4d0&quot;,
        &quot;terminal.bright_foreground&quot;: &quot;#f0e4d0&quot;,
        &quot;terminal.dim_foreground&quot;: &quot;#9a8d82&quot;,
        &quot;terminal.ansi.background&quot;: &quot;#201917&quot;,
        &quot;terminal.ansi.black&quot;: &quot;#191412&quot;,
        &quot;terminal.ansi.red&quot;: &quot;#fd6883&quot;,
        &quot;terminal.ansi.green&quot;: &quot;#adda78&quot;,
        &quot;terminal.ansi.yellow&quot;: &quot;#f9cc6c&quot;,
        &quot;terminal.ansi.blue&quot;: &quot;#f38d70&quot;,
        &quot;terminal.ansi.magenta&quot;: &quot;#a8a9eb&quot;,
        &quot;terminal.ansi.cyan&quot;: &quot;#85dacc&quot;,
        &quot;terminal.ansi.white&quot;: &quot;#f0e4d0&quot;,
        &quot;terminal.ansi.bright_black&quot;: &quot;#9a8d82&quot;,
        &quot;terminal.ansi.bright_red&quot;: &quot;#ff7a93&quot;,
        &quot;terminal.ansi.bright_green&quot;: &quot;#bdea86&quot;,
        &quot;terminal.ansi.bright_yellow&quot;: &quot;#ffdb80&quot;,
        &quot;terminal.ansi.bright_blue&quot;: &quot;#ff9d80&quot;,
        &quot;terminal.ansi.bright_magenta&quot;: &quot;#b9b9fb&quot;,
        &quot;terminal.ansi.bright_cyan&quot;: &quot;#96eadc&quot;,
        &quot;terminal.ansi.bright_white&quot;: &quot;#fff1f3&quot;,
        &quot;terminal.ansi.dim_black&quot;: &quot;#191412cc&quot;,
        &quot;terminal.ansi.dim_red&quot;: &quot;#fd6883cc&quot;,
        &quot;terminal.ansi.dim_green&quot;: &quot;#adda78cc&quot;,
        &quot;terminal.ansi.dim_yellow&quot;: &quot;#f9cc6ccc&quot;,
        &quot;terminal.ansi.dim_blue&quot;: &quot;#f38d70cc&quot;,
        &quot;terminal.ansi.dim_magenta&quot;: &quot;#a8a9ebcc&quot;,
        &quot;terminal.ansi.dim_cyan&quot;: &quot;#85dacccc&quot;,
        &quot;terminal.ansi.dim_white&quot;: &quot;#f0e4d0cc&quot;,
        &quot;link_text.hover&quot;: &quot;#85dacc&quot;,
        &quot;version_control.added&quot;: &quot;#adda78&quot;,
        &quot;version_control.modified&quot;: &quot;#f9cc6c&quot;,
        &quot;version_control.word_added&quot;: &quot;#adda7833&quot;,
        &quot;version_control.word_deleted&quot;: &quot;#fd688333&quot;,
        &quot;version_control.deleted&quot;: &quot;#fd6883&quot;,
        &quot;conflict&quot;: &quot;#f38d70&quot;,
        &quot;conflict.background&quot;: &quot;#f38d701f&quot;,
        &quot;conflict.border&quot;: &quot;#f38d7070&quot;,
        &quot;created&quot;: &quot;#adda78&quot;,
        &quot;created.background&quot;: &quot;#adda781f&quot;,
        &quot;created.border&quot;: &quot;#adda7870&quot;,
        &quot;deleted&quot;: &quot;#fd6883&quot;,
        &quot;deleted.background&quot;: &quot;#fd68831f&quot;,
        &quot;deleted.border&quot;: &quot;#fd688370&quot;,
        &quot;error&quot;: &quot;#fd6883&quot;,
        &quot;error.background&quot;: &quot;#fd68831f&quot;,
        &quot;error.border&quot;: &quot;#fd688370&quot;,
        &quot;hidden&quot;: &quot;#9a8d82&quot;,
        &quot;hidden.background&quot;: &quot;#9a8d821a&quot;,
        &quot;hidden.border&quot;: &quot;#9a8d8266&quot;,
        &quot;hint&quot;: &quot;#85dacc&quot;,
        &quot;hint.background&quot;: &quot;#85dacc1f&quot;,
        &quot;hint.border&quot;: &quot;#85dacc70&quot;,
        &quot;ignored&quot;: &quot;#9a8d82&quot;,
        &quot;ignored.background&quot;: &quot;#9a8d821a&quot;,
        &quot;ignored.border&quot;: &quot;#9a8d8266&quot;,
        &quot;info&quot;: &quot;#85dacc&quot;,
        &quot;info.background&quot;: &quot;#85dacc1f&quot;,
        &quot;info.border&quot;: &quot;#85dacc70&quot;,
        &quot;modified&quot;: &quot;#f9cc6c&quot;,
        &quot;modified.background&quot;: &quot;#f9cc6c1f&quot;,
        &quot;modified.border&quot;: &quot;#f9cc6c70&quot;,
        &quot;predictive&quot;: &quot;#9a8d82&quot;,
        &quot;predictive.background&quot;: &quot;#9a8d821a&quot;,
        &quot;predictive.border&quot;: &quot;#9a8d8266&quot;,
        &quot;renamed&quot;: &quot;#a8a9eb&quot;,
        &quot;renamed.background&quot;: &quot;#a8a9eb1f&quot;,
        &quot;renamed.border&quot;: &quot;#a8a9eb70&quot;,
        &quot;success&quot;: &quot;#adda78&quot;,
        &quot;success.background&quot;: &quot;#adda781f&quot;,
        &quot;success.border&quot;: &quot;#adda7870&quot;,
        &quot;unreachable&quot;: &quot;#9a8d82&quot;,
        &quot;unreachable.background&quot;: &quot;#9a8d821a&quot;,
        &quot;unreachable.border&quot;: &quot;#9a8d8266&quot;,
        &quot;warning&quot;: &quot;#f38d70&quot;,
        &quot;warning.background&quot;: &quot;#f38d701f&quot;,
        &quot;warning.border&quot;: &quot;#f38d7070&quot;,
        &quot;players&quot;: [
          {
            &quot;cursor&quot;: &quot;#f38d70&quot;,
            &quot;background&quot;: &quot;#f38d70&quot;,
            &quot;selection&quot;: &quot;#f38d703d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#fd6883&quot;,
            &quot;background&quot;: &quot;#fd6883&quot;,
            &quot;selection&quot;: &quot;#fd68833d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#adda78&quot;,
            &quot;background&quot;: &quot;#adda78&quot;,
            &quot;selection&quot;: &quot;#adda783d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#f9cc6c&quot;,
            &quot;background&quot;: &quot;#f9cc6c&quot;,
            &quot;selection&quot;: &quot;#f9cc6c3d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#a8a9eb&quot;,
            &quot;background&quot;: &quot;#a8a9eb&quot;,
            &quot;selection&quot;: &quot;#a8a9eb3d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#85dacc&quot;,
            &quot;background&quot;: &quot;#85dacc&quot;,
            &quot;selection&quot;: &quot;#85dacc3d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#f0e4d0&quot;,
            &quot;background&quot;: &quot;#f0e4d0&quot;,
            &quot;selection&quot;: &quot;#f0e4d03d&quot;
          },
          {
            &quot;cursor&quot;: &quot;#f38d70&quot;,
            &quot;background&quot;: &quot;#f38d70&quot;,
            &quot;selection&quot;: &quot;#f38d703d&quot;
          }
        ],
        &quot;syntax&quot;: {
          &quot;attribute&quot;: {
            &quot;color&quot;: &quot;#adda78&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;boolean&quot;: {
            &quot;color&quot;: &quot;#a8a9eb&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;comment&quot;: {
            &quot;color&quot;: &quot;#9a8d82&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;comment.doc&quot;: {
            &quot;color&quot;: &quot;#c3b3a5&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;constant&quot;: {
            &quot;color&quot;: &quot;#a8a9eb&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;constructor&quot;: {
            &quot;color&quot;: &quot;#85dacc&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;embedded&quot;: {
            &quot;color&quot;: &quot;#f0e4d0&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;emphasis&quot;: {
            &quot;color&quot;: &quot;#f38d70&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;emphasis.strong&quot;: {
            &quot;color&quot;: &quot;#a8a9eb&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: 700
          },
          &quot;enum&quot;: {
            &quot;color&quot;: &quot;#85dacc&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;function&quot;: {
            &quot;color&quot;: &quot;#adda78&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;hint&quot;: {
            &quot;color&quot;: &quot;#85dacc&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;keyword&quot;: {
            &quot;color&quot;: &quot;#fd6883&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;label&quot;: {
            &quot;color&quot;: &quot;#a8a9eb&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;link_text&quot;: {
            &quot;color&quot;: &quot;#f38d70&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;link_uri&quot;: {
            &quot;color&quot;: &quot;#85dacc&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;namespace&quot;: {
            &quot;color&quot;: &quot;#85dacc&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;number&quot;: {
            &quot;color&quot;: &quot;#a8a9eb&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;operator&quot;: {
            &quot;color&quot;: &quot;#fd6883&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;predictive&quot;: {
            &quot;color&quot;: &quot;#9a8d82&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;preproc&quot;: {
            &quot;color&quot;: &quot;#a8a9eb&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;primary&quot;: {
            &quot;color&quot;: &quot;#f0e4d0&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;property&quot;: {
            &quot;color&quot;: &quot;#f0e4d0&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation&quot;: {
            &quot;color&quot;: &quot;#9a8d82&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation.bracket&quot;: {
            &quot;color&quot;: &quot;#c3b3a5&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation.delimiter&quot;: {
            &quot;color&quot;: &quot;#c3b3a5&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation.list_marker&quot;: {
            &quot;color&quot;: &quot;#fd6883&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation.markup&quot;: {
            &quot;color&quot;: &quot;#fd6883&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;punctuation.special&quot;: {
            &quot;color&quot;: &quot;#ff7a93&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;selector&quot;: {
            &quot;color&quot;: &quot;#adda78&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;selector.pseudo&quot;: {
            &quot;color&quot;: &quot;#85dacc&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;string&quot;: {
            &quot;color&quot;: &quot;#f9cc6c&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;string.escape&quot;: {
            &quot;color&quot;: &quot;#a8a9eb&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;string.regex&quot;: {
            &quot;color&quot;: &quot;#f9cc6c&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;string.special&quot;: {
            &quot;color&quot;: &quot;#f38d70&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;string.special.symbol&quot;: {
            &quot;color&quot;: &quot;#f38d70&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;tag&quot;: {
            &quot;color&quot;: &quot;#fd6883&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;text.literal&quot;: {
            &quot;color&quot;: &quot;#f9cc6c&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;title&quot;: {
            &quot;color&quot;: &quot;#f9cc6c&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: 600
          },
          &quot;type&quot;: {
            &quot;color&quot;: &quot;#85dacc&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;variable&quot;: {
            &quot;color&quot;: &quot;#f0e4d0&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;variable.member&quot;: {
            &quot;color&quot;: &quot;#f0e4d0&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;variable.parameter&quot;: {
            &quot;color&quot;: &quot;#f38d70&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;variable.special&quot;: {
            &quot;color&quot;: &quot;#c3b3a5&quot;,
            &quot;font_style&quot;: &quot;italic&quot;,
            &quot;font_weight&quot;: null
          },
          &quot;variant&quot;: {
            &quot;color&quot;: &quot;#adda78&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;diff.plus&quot;: {
            &quot;color&quot;: &quot;#adda78&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          },
          &quot;diff.minus&quot;: {
            &quot;color&quot;: &quot;#fd6883&quot;,
            &quot;font_style&quot;: null,
            &quot;font_weight&quot;: null
          }
        }
      }
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content:encoded></item><item><title>SSH commit signing</title><link>https://dans.land/garden/ssh-commit-signing</link><guid isPermaLink="true">https://dans.land/garden/ssh-commit-signing</guid><description>How to sign Git commits with SSH on macOS.</description><pubDate>Sun, 05 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You can sign Git commits with an SSH key.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;The following uses the same SSH key for GitHub authentication and signing commits.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;create-a-new-ssh-key&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/ssh-commit-signing#create-a-new-ssh-key&quot;&gt;Create a new SSH key&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Create a new SSH key with a passphrase:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ssh-keygen -t ed25519 -C &quot;&amp;lt;ID&amp;gt;+&amp;lt;USERNAME&amp;gt;@users.noreply.github.com&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Use the following file location and name:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;~/.ssh/id_ed25519_github_danillouz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;I’m using the GitHub noreply email (which matches the noreply commit email) to &lt;a href=&quot;https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address#about-commit-email-addresses&quot;&gt;keep my email private&lt;/a&gt; (my primary email is also marked private on GitHub, so the noreply email is also used for web-based operations).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;update-ssh-config&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/ssh-commit-signing#update-ssh-config&quot;&gt;Update SSH config&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Add the following to &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;Host github.com
  AddKeysToAgent yes
  UseKeychain yes
  IdentityFile ~/.ssh/id_ed25519_github_danillouz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;add-private-key-to-the-ssh-agent&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/ssh-commit-signing#add-private-key-to-the-ssh-agent&quot;&gt;Add private key to the SSH agent&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Add the private key to the SSH agent, to automatically manage the key, and store the passphrase in the macOS keychain (the default macOS &lt;code&gt;ssh-add&lt;/code&gt; must be used):&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ssh-add --apple-use-keychain ~/.ssh/id_ed25519_github_danillouz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Then check it was added:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ssh-add -l&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;blockquote class=&quot;callout callout--tip&quot; data-callout=&quot;tip&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;✦&lt;/span&gt;Tip&lt;/p&gt;
&lt;p&gt;All previously added SSH key(s) can be deleted with &lt;code&gt;ssh-add -D&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;add-public-ssh-key-to-github&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/ssh-commit-signing#add-public-ssh-key-to-github&quot;&gt;Add public SSH key to GitHub&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Copy the public key:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pbcopy &amp;lt; ~/.ssh/id_ed25519_github_danillouz.pub&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;And &lt;a href=&quot;https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account&quot;&gt;add it to GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;When using the same SSH key for GitHub authentication and signing commits, the same key must be added twice to GitHub: once with type “authentication” and once with type “signing”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;test-ssh-connection&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/ssh-commit-signing#test-ssh-connection&quot;&gt;Test SSH connection&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Test the SSH connection to GitHub (authentication):&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;ssh -T git@github.com&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;Check that GitHub’s &lt;a href=&quot;https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints&quot;&gt;public key fingerprint&lt;/a&gt; matches before connecting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The GitHub username is printed when it works.&lt;/p&gt;
&lt;h2 id=&quot;tell-git-about-the-signing-key&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/ssh-commit-signing#tell-git-about-the-signing-key&quot;&gt;Tell Git about the signing key&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Update the global Git config to start using the SSH key to sign commits and tags:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git config --global user.signingkey &quot;~/.ssh/id_ed25519_github_danillouz.pub&quot;
git config --global gpg.format ssh
git config --global commit.gpgsign true
git config --global tag.gpgsign true&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;.gitconfig&lt;/code&gt; should now look like this:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[user]
  name = Daniël Illouz
  email = &amp;lt;ID&amp;gt;+&amp;lt;USERNAME&amp;gt;@users.noreply.github.com
  signingkey = ~/.ssh/id_ed25519_github_danillouz.pub
[gpg]
  format = ssh
[commit]
  gpgsign = true
[tag]
  gpgsign = true&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;local-signature-verification&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/ssh-commit-signing#local-signature-verification&quot;&gt;Local signature verification&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;SSH has no concept of trust levels like &lt;a href=&quot;https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#gpg-commit-signature-verification&quot;&gt;GPG&lt;/a&gt; does, but a file can be created that contains trusted SSH (public) keys.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;~/.config/git_allowed_signers&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Where each key in the file (each key must be placed on a separate line) has the format:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;&amp;lt;EMAIL&amp;gt; &amp;lt;KEY_TYPE&amp;gt; &amp;lt;PUBLIC_KEY&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Where &lt;code&gt;KEY_TYPE&lt;/code&gt; must be &lt;code&gt;ssh-ed25519&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To get &lt;code&gt;PUBLIC_KEY&lt;/code&gt; use:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pbcopy &amp;lt; ~/.ssh/id_ed25519_github_danillouz.pub&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Then update the global Git config to use the allowed signers file:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git config --global gpg.ssh.allowedSignersFile &quot;~/.config/git_allowed_signers&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;.gitconfig&lt;/code&gt; should now look like this:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[user]
  name = Daniël Illouz
  email = &amp;lt;ID&amp;gt;+&amp;lt;USERNAME&amp;gt;@users.noreply.github.com
  signingkey = ~/.ssh/id_ed25519_github_danillouz.pub
[gpg]
  format = ssh
[gpg &quot;ssh&quot;]
  allowedSignersFile = ~/.config/git_allowed_signers
[commit]
  gpgsign = true
[tag]
  gpgsign = true&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;With this config, signatures can be verified locally. For example with:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;git show --show-signature&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/ssh-commit-signing#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#ssh-commit-signature-verification&quot;&gt;SSH commit signature verification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent&quot;&gt;Generating a new SSH key and adding it to the ssh-agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/authentication/connecting-to-github-with-ssh/testing-your-ssh-connection&quot;&gt;Testing your SSH connection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgsshallowedSignersFile&quot;&gt;Git allowed signers file&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Cache stampeding</title><link>https://dans.land/garden/cache-stampeding</link><guid isPermaLink="true">https://dans.land/garden/cache-stampeding</guid><description>How to prevent cache stampedes.</description><pubDate>Sat, 04 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A cache stores data, so that future requests for that data can be served faster and more efficiently.&lt;/p&gt;
&lt;p&gt;Cached data can for example be the result of a previous (expensive) computation or a copy of data that’s stored elsewhere.&lt;/p&gt;
&lt;p&gt;When a system using a cache comes under high (concurrent) load, a specific type of failure can happen when a cache miss occurs.&lt;/p&gt;
&lt;p&gt;In that cache miss scenario, many concurrent requests will try to revalidate the cache at the same time.
For example, by fetching data from an origin, like a database.&lt;/p&gt;
&lt;p&gt;This can lead to congestion and resource issues that take down the system.
The failure is called a cache stampede (also known as a “cache thundering herd” or “cache dog piling”).&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;This is why “just adding a cache” to a slow system can be dangerous, and why highly available and low-latency systems use techniques like &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/low-latency-ha#constant-work&quot; data-garden-link=&quot;&quot;&gt;constant work&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;mitigations&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/cache-stampeding#mitigations&quot;&gt;Mitigations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These are some techniques to prevent cache stampedes.&lt;/p&gt;
&lt;h3 id=&quot;locking&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/cache-stampeding#locking&quot;&gt;Locking&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By using a “cache lock”, only 1 request will revalidate the cache, while the other requests wait (or return stale data, or a “not found”).&lt;/p&gt;
&lt;p&gt;This is for example &lt;a href=&quot;https://developers.cloudflare.com/cache/concepts/default-cache-behavior/#request-collapsing&quot;&gt;what Cloudflare does&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This approach works well in most cases.
But the main downside is that it typically requires a “lock service”, which adds latency (and fallibility).&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;The lock-service tradeoff applies to coordination across multiple instances.
Within one process, an in-memory lock or single-flight mechanism can be enough.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;probabilistic-early-revalidation&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/cache-stampeding#probabilistic-early-revalidation&quot;&gt;Probabilistic (early) revalidation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By using a probabilistic decision, fewer requests need to revalidate the cache at the same time:
it basically rolls a die if it should revalidate the cache.&lt;/p&gt;
&lt;p&gt;This approach does not have the downsides of using a cache lock, but needs to take &lt;a href=&quot;https://blog.cloudflare.com/sometimes-i-cache/#optimal-cache-stampede-solution&quot;&gt;different request rates into account&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;external-revalidation&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/cache-stampeding#external-revalidation&quot;&gt;External revalidation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By using “something external”, the cache can be revalidated proactively.
For example, by pushing fresh data to the cache at the appropriate time.&lt;/p&gt;</content:encoded></item><item><title>Low-latency HA</title><link>https://dans.land/garden/low-latency-ha</link><guid isPermaLink="true">https://dans.land/garden/low-latency-ha</guid><description>Low-latency and high-availability (HA) patterns I learned from DynamoDB and S3.</description><pubDate>Sun, 15 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After watching the re:Invent 2024 DynamoDB (&lt;a href=&quot;https://www.youtube.com/watch?v=Qzs8mU5dgx4&quot;&gt;DAT406&lt;/a&gt;) and S3 (&lt;a href=&quot;https://www.youtube.com/watch?v=NXehLy7IiPM&quot;&gt;STG302&lt;/a&gt;) deep dives,
I learned that DynamoDB uses an in-memory data store (MemDS) that serves their request routers.&lt;/p&gt;
&lt;p&gt;DynamoDB is a low-latency and highly available system.
AWS reports &lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TroubleshootingLatency.html&quot;&gt;single-digit-millisecond average service latency&lt;/a&gt; for many single-item operations,
excluding client and network overhead, and I learned it uses &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/low-latency-ha#request-hedging&quot;&gt;Request hedging&lt;/a&gt; and &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/low-latency-ha#constant-work&quot;&gt;Constant work&lt;/a&gt; as design patterns to help achieve this.&lt;/p&gt;
&lt;p&gt;S3 also uses a pattern called &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/low-latency-ha#shuffle-sharding&quot;&gt;Shuffle sharding&lt;/a&gt; (I’m not sure if DynamoDB also uses that, but it seems likely to me).&lt;/p&gt;
&lt;h2 id=&quot;request-hedging&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/low-latency-ha#request-hedging&quot;&gt;Request hedging&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Request hedging is a design pattern to deal with tail latencies in distributed systems.
It’s both used by &lt;a href=&quot;https://youtu.be/Qzs8mU5dgx4?t=1741&quot;&gt;DynamoDB&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/watch?v=NXehLy7IiPM&amp;amp;t=1816s&quot;&gt;S3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The main idea is to make a second request, and use the fastest response.&lt;/p&gt;
&lt;p&gt;This works when the requests take sufficiently independent paths.
If the first request is in the tail, the second request may still complete sooner.&lt;/p&gt;
&lt;p&gt;Request hedging is a bet.
It will not always pay off.
But in practice, it’s very effective.
Dean and Barroso describe this pattern in &lt;a href=&quot;https://www.barroso.org/publications/TheTailAtScale.pdf&quot;&gt;The Tail at Scale&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;The second request can start immediately or after a delay.
Hedging adds work and load, so only use it for operations that are safe to duplicate, and cancel the slower request when possible.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;shuffle-sharding&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/low-latency-ha#shuffle-sharding&quot;&gt;Shuffle sharding&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Shuffle sharding is a design pattern &lt;a href=&quot;https://www.youtube.com/watch?v=NXehLy7IiPM&amp;amp;t=1254s&quot;&gt;used by S3&lt;/a&gt; to allocate workloads across subsets of resources with limited overlap.
This isolates contention and “hot” workloads, by keeping their blast radius bounded.&lt;/p&gt;
&lt;p&gt;Shuffle sharding can make request hedging more effective when it helps send the hedged requests through sufficiently independent paths.
It does not guarantee independence.&lt;/p&gt;
&lt;p&gt;But shuffle sharding also helps achieve high availability:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;By spreading workloads across partially overlapping subsets, a failure is less likely to affect every possible path.&lt;/li&gt;
&lt;li&gt;By &lt;a href=&quot;https://aws.amazon.com/builders-library/workload-isolation-using-shuffle-sharding/&quot;&gt;isolating workloads to handle DDoS attacks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;power-of-two-random-choices&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/low-latency-ha#power-of-two-random-choices&quot;&gt;Power of two random choices&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Assigning requests completely at random can still leave some resources much busier than others.
A surprisingly effective improvement is to &lt;a href=&quot;https://youtu.be/NXehLy7IiPM?t=2053&quot;&gt;sample two resources and pick the less busy one&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;constant-work&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/low-latency-ha#constant-work&quot;&gt;Constant work&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;DynamoDB uses a design pattern called &lt;a href=&quot;https://youtu.be/Qzs8mU5dgx4?t=1826&quot;&gt;constant work&lt;/a&gt; to make sure a (part of a) system always runs in the same “steady state”.
This prevents potentially overloading the system, and helps make it highly available.&lt;/p&gt;
&lt;p&gt;This is especially important for systems that use (in-memory) caches.&lt;/p&gt;
&lt;p&gt;DynamoDB’s request routers have their own in-memory cache, but even for cache hits,
they send request(s) to their storage (MemDS),
so that when they lose the in-memory cache in the router for whatever reason,
the storage “doesn’t notice”.&lt;/p&gt;</content:encoded></item><item><title>pprof reports</title><link>https://dans.land/garden/go/pprof-reports</link><guid isPermaLink="true">https://dans.land/garden/go/pprof-reports</guid><description>What I learned from reading pprof reports so far.</description><pubDate>Sun, 24 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Go provides the &lt;a href=&quot;https://github.com/google/pprof&quot;&gt;pprof&lt;/a&gt; tool to visualize and analyze collected pprof profiles&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/go/pprof-reports#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&quot;installation&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pprof-reports#installation&quot;&gt;Installation&lt;/a&gt;&lt;/h2&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go install github.com/google/pprof@latest&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;usage&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pprof-reports#usage&quot;&gt;Usage&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote class=&quot;callout callout--tip&quot; data-callout=&quot;tip&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;✦&lt;/span&gt;Tip&lt;/p&gt;
&lt;p&gt;When using &lt;code&gt;pprof&lt;/code&gt;, the &lt;code&gt;binary&lt;/code&gt; is optional, but very useful: when provided, &lt;code&gt;pprof&lt;/code&gt; can show source code (e.g. when using &lt;code&gt;list&lt;/code&gt;, see below).&lt;/p&gt;
&lt;p&gt;From what I understand, you don’t have to provide the (test) binary when creating a profile from &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/go/benchmarking#profiling-benchmarks&quot; data-garden-link=&quot;&quot;&gt;benchmarks&lt;/a&gt;: this will happen implicitly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;tui&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pprof-reports#tui&quot;&gt;TUI&lt;/a&gt;&lt;/h3&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go tool pprof [binary] profiles&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Useful commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;top&lt;/code&gt; shows the “top 10” nodes.
&lt;ul&gt;
&lt;li&gt;You can also use &lt;code&gt;topN&lt;/code&gt; to list more (e.g. &lt;code&gt;top25&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tree&lt;/code&gt; or &lt;code&gt;tree functionName&lt;/code&gt; shows a textual call graph.
&lt;ul&gt;
&lt;li&gt;But I often find the &lt;a href=&quot;https://dans.land/garden/go/pprof-reports#web-ui&quot;&gt;Web UI&lt;/a&gt; Graph view nicer to explore.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list functionName&lt;/code&gt; or &lt;code&gt;weblist functionName&lt;/code&gt; shows the source code.
&lt;ul&gt;
&lt;li&gt;And why providing the binary is useful.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;peek functionName&lt;/code&gt; shows callers and callees.
&lt;ul&gt;
&lt;li&gt;But I often find the &lt;a href=&quot;https://dans.land/garden/go/pprof-reports#web-ui&quot;&gt;Web UI&lt;/a&gt; Flame Graph view nicer to explore (right-click a function to see code).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;web-ui&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pprof-reports#web-ui&quot;&gt;Web UI&lt;/a&gt;&lt;/h3&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go tool pprof -http=:3000 [binary] profiles&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This will show the Graph view by default: a call graph where every node represents a function.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--tip&quot; data-callout=&quot;tip&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;✦&lt;/span&gt;Tip&lt;/p&gt;
&lt;p&gt;Not all nodes may show in the Graph view by default.
When this happens, it’s indicated by dashed arrows between nodes.
To make them visible, either increase the node count (e.g. &lt;code&gt;-nodecount=500&lt;/code&gt;), or focus on a function name (e.g. use the search box).&lt;/p&gt;
&lt;p&gt;If you want to include all data of the profile, add the &lt;code&gt;-nodefraction=0&lt;/code&gt; option when running pprof or type &lt;code&gt;nodefraction=0&lt;/code&gt; in the TUI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;memory-profiles&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pprof-reports#memory-profiles&quot;&gt;Memory profiles&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;in-use-vs-alloc&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pprof-reports#in-use-vs-alloc&quot;&gt;In-use vs alloc&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;In-use&lt;/strong&gt; shows the memory currently being held.
&lt;ul&gt;
&lt;li&gt;Visualizes the memory that’s “live” on the heap.&lt;/li&gt;
&lt;li&gt;Use this view when the amount of memory being used is a problem.
&lt;ul&gt;
&lt;li&gt;TUI: type &lt;code&gt;inuse_space&lt;/code&gt; command to select (default view).&lt;/li&gt;
&lt;li&gt;Web UI: select &lt;code&gt;Sample &amp;gt; inuse_space&lt;/code&gt; (default view).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alloc&lt;/strong&gt; shows the total memory allocations from the beginning, &lt;em&gt;including&lt;/em&gt; already freed memory.
&lt;ul&gt;
&lt;li&gt;Visualizes the part(s) allocating the most memory.&lt;/li&gt;
&lt;li&gt;Use this view when time spent in garbage collection is a problem.
&lt;ul&gt;
&lt;li&gt;TUI: type &lt;code&gt;alloc_space&lt;/code&gt; command to select.&lt;/li&gt;
&lt;li&gt;Web UI: select &lt;code&gt;Sample &amp;gt; alloc_space&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;flat-vs-cum&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pprof-reports#flat-vs-cum&quot;&gt;Flat vs cum&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flat&lt;/strong&gt; shows the direct memory usage of a function, &lt;em&gt;excluding&lt;/em&gt; the memory used by its callees.
&lt;ul&gt;
&lt;li&gt;Visualizes how much memory a function is directly responsible for using.&lt;/li&gt;
&lt;li&gt;A high flat value means the function itself is a significant contributor.&lt;/li&gt;
&lt;li&gt;In the Web UI Graph view, high flat values are indicated by a large font size.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cum&lt;/strong&gt; (cumulative) shows the memory usage of a function and all its callees.
&lt;ul&gt;
&lt;li&gt;Visualizes the overall impact of a function on a program’s memory usage.&lt;/li&gt;
&lt;li&gt;A high cumulative value means the function and its callees are a significant contributor.&lt;/li&gt;
&lt;li&gt;In the Web UI Graph view, high cum values are indicated by a large red box (grey means close to zero).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pprof-reports#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google/pprof/blob/main/doc/README.md&quot;&gt;pprof README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://go.dev/blog/pprof&quot;&gt;Profiling Go Programs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://go.dev/src/runtime/mprof.go&quot;&gt;Runtime mprof&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://go.dev/src/runtime/pprof/pprof.go&quot;&gt;Runtime pprof&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 id=&quot;footnote-label&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pprof-reports#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;A &lt;a href=&quot;https://pkg.go.dev/runtime/pprof#Profile&quot;&gt;profile&lt;/a&gt; is a sampled collection of stack traces. Profiles can be collected via &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/go/benchmarking#profiling-benchmarks&quot; data-garden-link=&quot;&quot;&gt;benchmarks&lt;/a&gt;, by exposing &lt;a href=&quot;https://pkg.go.dev/net/http/pprof&quot;&gt;HTTP debug endpoints&lt;/a&gt;, or &lt;a href=&quot;https://pkg.go.dev/runtime/pprof#Lookup&quot;&gt;programmatically&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/go/pprof-reports#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>Deno permissions</title><link>https://dans.land/garden/deno-permissions</link><guid isPermaLink="true">https://dans.land/garden/deno-permissions</guid><description>Deno&apos;s permissions model can fail GitHub Actions jobs when creating job summaries.</description><pubDate>Sat, 09 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;TL;DR&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To create a GitHub Actions job summary from a Deno script, the script must run with &lt;code&gt;--allow-env&lt;/code&gt;, &lt;code&gt;--allow-read&lt;/code&gt;, &lt;code&gt;--allow-sys&lt;/code&gt; and &lt;code&gt;--allow-write&lt;/code&gt; permissions.&lt;/li&gt;
&lt;li&gt;Use Deno &lt;code&gt;--no-prompt&lt;/code&gt; to turn permission prompts into immediate errors when running interactively.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt; has a cool feature to create &lt;a href=&quot;https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#adding-a-job-summary&quot;&gt;job summaries&lt;/a&gt;.
This lets you add custom Markdown and/or HTML to a job and show it on the summary page of a workflow run (e.g. to create custom reports).&lt;/p&gt;
&lt;h2 id=&quot;how-to-create-a-job-summary&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/deno-permissions#how-to-create-a-job-summary&quot;&gt;How to create a job summary&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The simplest way is to write to the &lt;code&gt;$GITHUB_STEP_SUMMARY&lt;/code&gt; environment variable:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;steps:
  - name: Create job summary
    run: |
      echo &quot;My report&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
      echo &quot;&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
      echo &quot;This is a Markdown list:&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
      echo &quot;&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
      echo &quot;- One&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY
      echo &quot;- Two&quot; &amp;gt;&amp;gt; $GITHUB_STEP_SUMMARY&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;But there’s also the &lt;a href=&quot;https://github.com/actions/toolkit/tree/main/packages/core#populating-job-summary&quot;&gt;@actions/core&lt;/a&gt; toolkit that allows doing the above in JavaScript/TypeScript:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import * as core from &quot;@actions/core&quot;

await core.summary
  .addHeading(&quot;My report&quot;)
  .addEOL()
  .addRaw(&quot;This is a Markdown list:&quot;)
  .addEOL()
  .addList([&quot;One&quot;, &quot;Two&quot;])
  .write()&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/deno-permissions#the-problem&quot;&gt;The problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I was using the &lt;code&gt;@actions/core&lt;/code&gt; toolkit in a &lt;a href=&quot;https://deno.com/&quot;&gt;Deno&lt;/a&gt; script (executed in a workflow job) to create a job summary.
But my job would always fail.&lt;/p&gt;
&lt;p&gt;For some reason, the Promise creating the job summary would never resolve:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;error: Top-level await promise never resolved&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;the-code&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/deno-permissions#the-code&quot;&gt;The code&lt;/a&gt;&lt;/h2&gt;
&lt;figure&gt;&lt;figcaption&gt;mod.ts&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import * as core from &quot;npm:@actions/core&quot;

export async function createJobSummary&amp;lt;T extends Record&amp;lt;string, any&amp;gt;&amp;gt;(items: T[], title: string) {
  let buff = core.summary.addHeading(title).addEOL()
  if (items.length &amp;lt; 1) {
    buff = buff.addRaw(`No data`).addEOL()
  } else {
    const columns = Object.keys(items[0])
    const header = columns.map((col) =&amp;gt; {
      return {
        data: col.toUpperCase(),
        header: true,
      }
    })
    const rows = items.map((item) =&amp;gt; {
      return columns.map((col) =&amp;gt; String(item[col]))
    })
    buff = buff.addTable([header, ...rows])
  }
  return buff.write()
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;why-it-fails&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/deno-permissions#why-it-fails&quot;&gt;Why it fails&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Creating a job summary essentially &lt;a href=&quot;https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables&quot;&gt;writes to a file&lt;/a&gt;.
This becomes obvious when checking the toolkit’s &lt;a href=&quot;https://github.com/actions/toolkit/blob/193fa46c20fde8b0ed54194bc08b841c78c0776d/packages/core/src/summary.ts#L117-L130&quot;&gt;write&lt;/a&gt; code.&lt;/p&gt;
&lt;p&gt;But by default, Deno doesn’t have access to sensitive APIs.
For example, it does not have &lt;a href=&quot;https://docs.deno.com/runtime/reference/permissions/#file-system-access&quot;&gt;permission to access the file system&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The error hid the underlying cause:
the toolkit could not access the summary file without the required permissions.&lt;/p&gt;
&lt;h2 id=&quot;the-fix&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/deno-permissions#the-fix&quot;&gt;The fix&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Job summaries write to a file, so the Deno script must run with &lt;code&gt;--allow-write&lt;/code&gt; permission.
But interestingly this didn’t fix the issue: my job would still fail with the same error.&lt;/p&gt;
&lt;p&gt;This left me puzzled.
So after a while I just tried running the script with &lt;code&gt;--allow-all&lt;/code&gt; permission, and it worked.&lt;/p&gt;
&lt;p&gt;Turns out that the toolkit also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/actions/toolkit/blob/193fa46c20fde8b0ed54194bc08b841c78c0776d/packages/core/src/summary.ts#L62-L90&quot;&gt;Reads an environment variable and checks whether the summary file is readable and writable&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Uses Node compatibility APIs that access system information.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And Deno requires explicit permission to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.deno.com/runtime/reference/permissions/#file-system-access&quot;&gt;Read and write files&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.deno.com/runtime/reference/permissions/#environment-variables&quot;&gt;Access environment variables&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.deno.com/runtime/reference/permissions/#system-information&quot;&gt;Access system information&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the script must be run with &lt;code&gt;--allow-env&lt;/code&gt;, &lt;code&gt;--allow-read&lt;/code&gt;, &lt;code&gt;--allow-sys&lt;/code&gt; and &lt;code&gt;--allow-write&lt;/code&gt; permissions to create a job summary.&lt;/p&gt;
&lt;h2 id=&quot;improving-permission-errors&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/deno-permissions#improving-permission-errors&quot;&gt;Improving permission errors&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Debugging permission errors as described above isn’t great.
Can we make the script fail (faster) with a better error?&lt;/p&gt;
&lt;p&gt;Deno prompts for missing permissions when running interactively.
We can disable this behavior with &lt;code&gt;--no-prompt&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote cite=&quot;https://docs.deno.com/runtime/fundamentals/security/#permissions&quot;&gt;
&lt;p&gt;Prompts are not shown if stdout/stderr are not a TTY, or when the &lt;code&gt;--no-prompt&lt;/code&gt; flag is passed to the &lt;code&gt;deno&lt;/code&gt; command.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://docs.deno.com/runtime/fundamentals/security/#permissions&quot;&gt;https://docs.deno.com/runtime/fundamentals/security/#permissions&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;p&gt;When this flag is used, Deno returns a clearer permission error (and fails faster).
Prompts are already disabled when stdout and stderr are not attached to a TTY, as is normally the case in GitHub Actions:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;error: Uncaught (in promise) NotCapable: Requires sys access to &quot;uid&quot;, run again with the --allow-sys flag&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;example-run-task&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/deno-permissions#example-run-task&quot;&gt;Example run task&lt;/a&gt;&lt;/h2&gt;
&lt;figure&gt;&lt;figcaption&gt;deno.json&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;tasks&quot;: {
    &quot;run&quot;: &quot;deno run --allow-env --allow-read --allow-sys --allow-write --no-prompt mod.ts&quot;
  },
  &quot;imports&quot;: {
    &quot;@actions/core&quot;: &quot;npm:@actions/core@^1.11.1&quot;
  },
  &quot;fmt&quot;: {
    &quot;semiColons&quot;: false
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/deno-permissions#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.blog/news-insights/product-news/supercharging-github-actions-with-job-summaries/&quot;&gt;GitHub Actions Job Summaries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/actions/toolkit/tree/main&quot;&gt;GitHub Actions Toolkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.deno.com/runtime/fundamentals/security/&quot;&gt;Deno security and permissions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>ZIP bomb countermeasures</title><link>https://dans.land/garden/go/zip-bomb-countermeasures</link><guid isPermaLink="true">https://dans.land/garden/go/zip-bomb-countermeasures</guid><description>How to defend against ZIP bombs in Go.</description><pubDate>Fri, 08 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A ZIP bomb is a malicious &lt;a href=&quot;https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT&quot;&gt;ZIP archive&lt;/a&gt; designed to crash the program or system reading it.
When such a ZIP archive is extracted, it expands to terabytes or even petabytes of data&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/go/zip-bomb-countermeasures#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, which would quickly overwhelm most systems.&lt;/p&gt;
&lt;p&gt;This is why ZIP bombs are often used in attacks to disable antivirus scanners,
crash file processing services,
or conduct denial-of-service attacks against systems that (automatically) extract archived files.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/zip-bomb-countermeasures#how-it-works&quot;&gt;How it works&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;ZIP is a container format (and not a compression algorithm).
A ZIP archive contains a &lt;strong&gt;central directory&lt;/strong&gt;, which is basically a list of headers that reference the actual files in the archive.
The files in the ZIP archive are often compressed using &lt;a href=&quot;https://en.wikipedia.org/wiki/Deflate&quot;&gt;DEFLATE&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;ZIP bombs achieve extreme compression ratios by exploiting the container format:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Recursive ZIP bombs&lt;/strong&gt; contain nested ZIP files within ZIP files that create a chain reaction when extracted. But this only works if the program can read ZIP archives recursively.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Non-recursive ZIP bombs&lt;/strong&gt; &lt;a href=&quot;https://www.bamsoftware.com/hacks/zipbomb/&quot;&gt;overlap compressed files in the ZIP archive&lt;/a&gt;. This works by first creating a highly compressed file (e.g. a long string of repeated bytes), and then making (all) the headers in the ZIP’s central directory reference that compressed file. This technique can achieve compression ratios over 28 million, far beyond &lt;a href=&quot;https://www.zlib.net/zlib_tech.html&quot;&gt;DEFLATE’s compression ratio of 1032&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So how can we defend against this in Go?&lt;/p&gt;
&lt;h2 id=&quot;countermeasures&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/zip-bomb-countermeasures#countermeasures&quot;&gt;Countermeasures&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When reading ZIP archives in Go, the following works in our favor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go’s &lt;a href=&quot;https://pkg.go.dev/archive/zip#Reader&quot;&gt;zip.Reader&lt;/a&gt; does &lt;a href=&quot;https://cs.opensource.google/go/go/+/refs/tags/go1.23.3:src/archive/zip/reader.go;l=145-156&quot;&gt;not read recursively&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Non-recursive ZIP bombs can be detected by their characteristics:
&lt;ul&gt;
&lt;li&gt;Many headers will point to the same compressed data (i.e. “many files” in the ZIP archive).&lt;/li&gt;
&lt;li&gt;Files in the ZIP archive will typically have an unusually high compression ratio.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While Go currently lacks a &lt;a href=&quot;https://github.com/golang/go/issues/33036&quot;&gt;resource limits API&lt;/a&gt;, we can easily implement the following countermeasures:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For the ZIP archive:
&lt;ul&gt;
&lt;li&gt;Limit the number of allowed files.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For the files in the ZIP archive:
&lt;ul&gt;
&lt;li&gt;Apply a maximum uncompressed size.&lt;/li&gt;
&lt;li&gt;Apply a maximum compression ratio.&lt;/li&gt;
&lt;li&gt;Limit the number of bytes that can actually be read.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;const (
  MaxZipFiles                 = 1_000
  MaxZipFileUncompressedBytes = 50 * 1024 * 1024 // 50 MiB
  MaxZipFileCompressionRatio  = 1_032            // Based on DEFLATE max compression ratio
)

// ValidateZip validates a ZIP archive.
func ValidateZip(r *zip.Reader) error {
  if len(r.File) &amp;gt; MaxZipFiles {
    return fmt.Errorf(&quot;too many files&quot;)
  }

  for _, file := range r.File {
    // ZIP metadata is attacker-controlled: use it for early rejection only.
    // A valid checksum does not authenticate the metadata.
    // [ReadSafeZipFile] also limits the actual bytes read and propagates errors.
    compSize := file.CompressedSize64
    uncompSize := file.UncompressedSize64
    if uncompSize &amp;gt; uint64(MaxZipFileUncompressedBytes) {
      return fmt.Errorf(&quot;file %s too large&quot;, file.Name)
    }
    if compSize &amp;gt; 0 &amp;amp;&amp;amp; uncompSize &amp;gt; compSize {
      // compSize is smaller than the already-limited uncompSize,
      // so this multiplication cannot overflow.
      if uncompSize &amp;gt; uint64(MaxZipFileCompressionRatio)*compSize {
        return fmt.Errorf(&quot;suspiciously high compression ratio&quot;)
      }
    }
  }

  return nil
}

// ReadSafeZipFile reads a ZIP member while rejecting output over the limit.
func ReadSafeZipFile(r io.Reader) ([]byte, error) {
  limited := io.LimitReader(r, MaxZipFileUncompressedBytes+1)
  b, err := io.ReadAll(limited)
  if err != nil {
    return nil, err
  }

  if len(b) &amp;gt; MaxZipFileUncompressedBytes {
    return nil, fmt.Errorf(&quot;file exceeds uncompressed size limit&quot;)
  }

  return b, nil
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Open each member and pass the returned reader to &lt;code&gt;ReadSafeZipFile&lt;/code&gt;.
Keep and handle the error so checksum failures and oversized output are rejected:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;fileReader, err := file.Open()
if err != nil {
  return err
}
defer fileReader.Close()

contents, err := ReadSafeZipFile(fileReader)
if err != nil {
  return err
}

_ = contents&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 id=&quot;footnote-label&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/zip-bomb-countermeasures#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;The notorious &lt;a href=&quot;https://unforgettable.dk/&quot;&gt;42.zip&lt;/a&gt; expands from 42 kilobytes to 4.5 petabytes. &lt;a href=&quot;https://dans.land/garden/go/zip-bomb-countermeasures#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>S3 upload memory</title><link>https://dans.land/garden/go/s3-upload-memory</link><guid isPermaLink="true">https://dans.land/garden/go/s3-upload-memory</guid><description>How to prevent high memory usage when uploading many files via the Go S3 manager uploader.</description><pubDate>Sun, 27 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/s3/manager#Uploader&quot;&gt;&lt;code&gt;manager.Uploader&lt;/code&gt;&lt;/a&gt; is now deprecated in favor of &lt;code&gt;feature/s3/transfermanager&lt;/code&gt;.
This post describes the SDK version used for the original investigation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are 2 options to upload files to S3 using the &lt;a href=&quot;https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/welcome.html&quot;&gt;Go V2 AWS SDK&lt;/a&gt; (besides using &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html&quot;&gt;presigned URLs&lt;/a&gt;):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/s3#Client.PutObject&quot;&gt;S3 client put object&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/s3/manager#Uploader.Upload&quot;&gt;S3 manager uploader upload&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both accept &lt;a href=&quot;https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/s3#PutObjectInput&quot;&gt;PutObjectInput&lt;/a&gt;, where the &lt;code&gt;Body&lt;/code&gt; must be an &lt;code&gt;io.Reader&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At the time, option 2 was recommended when uploading many (large) files, because it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Safely uploads files concurrently across goroutines.&lt;/li&gt;
&lt;li&gt;Buffers large files into smaller chunks and uploads them in parallel.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/s3-upload-memory#the-problem&quot;&gt;The problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While working on a project that needed to upload zip archives containing lots of files, I chose the S3 manager uploader for its concurrent upload capabilities.&lt;/p&gt;
&lt;p&gt;But I quickly ran into memory issues: when uploading zip archives that contain hundreds of files, my Go service would often run out of memory (OOM).&lt;/p&gt;
&lt;p&gt;For example, uploading a zip archive with ~100 files caused the service memory usage to consistently spike to ~500 MiB.&lt;/p&gt;
&lt;h3 id=&quot;the-code&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/s3-upload-memory#the-code&quot;&gt;The code&lt;/a&gt;&lt;/h3&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package s3

import (
  &quot;archive/zip&quot;
  &quot;context&quot;
  &quot;fmt&quot;
  &quot;mime&quot;
  &quot;path/filepath&quot;

  &quot;github.com/aws/aws-sdk-go-v2/aws&quot;
  &quot;github.com/aws/aws-sdk-go-v2/feature/s3/manager&quot;
  &quot;github.com/aws/aws-sdk-go-v2/service/s3&quot;
  &quot;golang.org/x/sync/errgroup&quot;
)

type Uploader struct {
  uploader *manager.Uploader
  bucket   string
}

func NewUploader(client manager.UploadAPIClient, bucket string) *Uploader {
  return &amp;amp;Uploader{
    uploader: manager.NewUploader(client),
    bucket:   bucket,
  }
}

func (u *Uploader) UploadZip(ctx context.Context, zipr *zip.Reader) error {
  group, ctx := errgroup.WithContext(ctx)
  for _, file := range zipr.File {
    group.Go(func() error {
      return u.uploadZipFile(ctx, file)
    })
  }
  if err := group.Wait(); err != nil {
    return fmt.Errorf(&quot;uploading zip file: %v&quot;, err)
  }
  return nil
}

func (u *Uploader) uploadZipFile(ctx context.Context, file *zip.File) error {
  zf, err := file.Open()
  if err != nil {
    return err
  }
  defer zf.Close()

  mimeType := detectMimeType(file.Name)
  _, err = u.uploader.Upload(ctx, &amp;amp;s3.PutObjectInput{
    Bucket:      aws.String(u.bucket),
    Key:         aws.String(file.Name),
    Body:        zf,
    ContentType: aws.String(mimeType),
  })
  return err
}

func detectMimeType(fileName string) string {
  ext := filepath.Ext(fileName)
  mimeType := mime.TypeByExtension(ext)
  if AllowedMimeType(mimeType) {
    // Use an allow list for improved security.
    return mimeType
  }

  return &quot;application/octet-stream&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Opening a file in the zip archive returns an &lt;code&gt;io.ReadCloser&lt;/code&gt;, and passing that to the &lt;code&gt;Body&lt;/code&gt; when uploading should stream the contents of the file efficiently to S3. So why the memory issues?&lt;/p&gt;
&lt;p&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/go/benchmarking#profiling-benchmarks&quot; data-garden-link=&quot;&quot;&gt;Profiling using a benchmark&lt;/a&gt; didn’t show any issues. So I started digging into the S3 manager code.&lt;/p&gt;
&lt;h3 id=&quot;root-cause-default-part-size&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/s3-upload-memory#root-cause-default-part-size&quot;&gt;Root cause: default part size&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The S3 manager uploader memory behavior is controlled by the &lt;a href=&quot;https://github.com/aws/aws-sdk-go-v2/blob/feature/s3/manager/v1.17.25/feature/s3/manager/upload.go#L275&quot;&gt;PartSize&lt;/a&gt; parameter. By default, it’s set to &lt;a href=&quot;https://github.com/aws/aws-sdk-go-v2/blob/feature/s3/manager/v1.17.25/feature/s3/manager/upload.go#L27-L33&quot;&gt;5 MiB&lt;/a&gt; and is also used in the &lt;a href=&quot;https://github.com/aws/aws-sdk-go-v2/blob/feature/s3/manager/v1.17.25/feature/s3/manager/upload.go#L286&quot;&gt;memory pool&lt;/a&gt; (so the allocated buffer memory can be reused between uploads).&lt;/p&gt;
&lt;p&gt;This is the interesting part: &lt;a href=&quot;https://github.com/aws/aws-sdk-go-v2/blob/feature/s3/manager/v1.17.25/feature/s3/manager/upload.go#L461-L474&quot;&gt;by default&lt;/a&gt; the uploader allocates the 5 MiB buffer for &lt;em&gt;every&lt;/em&gt; file being uploaded, &lt;em&gt;regardless&lt;/em&gt; of the file’s actual size.&lt;/p&gt;
&lt;p&gt;With a plain &lt;code&gt;io.Reader&lt;/code&gt;, the uploader buffers parts before uploading them:
it does not need to hold the entire file in memory.
If the body implements both &lt;code&gt;io.ReadSeeker&lt;/code&gt; and &lt;code&gt;io.ReaderAt&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/go/s3-upload-memory#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;,
the uploader can determine its size and read parts directly,
&lt;a href=&quot;https://github.com/aws/aws-sdk-go-v2/blob/feature/s3/manager/v1.17.25/feature/s3/manager/upload.go#L432-L459&quot;&gt;avoiding those buffers&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-fix&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/s3-upload-memory#the-fix&quot;&gt;The fix&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://pkg.go.dev/archive/zip#File.Open&quot;&gt;Opening a zip file&lt;/a&gt; returns an &lt;code&gt;io.ReadCloser&lt;/code&gt;. Writing it to a temporary file gives the uploader a body that implements both &lt;code&gt;io.ReadSeeker&lt;/code&gt; and &lt;code&gt;io.ReaderAt&lt;/code&gt;, which can prevent the extra buffering while still using the S3 manager to upload files concurrently.&lt;/p&gt;
&lt;p&gt;I think the simplest options to do this are (before uploading):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write each file in the zip archive to a temporary file.&lt;/li&gt;
&lt;li&gt;Read each file in the zip archive into memory using &lt;code&gt;io.ReadAll()&lt;/code&gt; and &lt;code&gt;bytes.NewReader()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After testing both, I found using option 1 to be the (slightly) better choice.
While both methods had similar memory overhead, writing to a temporary file used (slightly) less CPU and had (slightly) less GC (Garbage Collector) overhead.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;Each active upload has its own part concurrency.
For streaming bodies, buffers can use roughly &lt;code&gt;PartSize * Concurrency&lt;/code&gt; per upload, so also limit the outer loop.
For example, with &lt;code&gt;group.SetLimit(8)&lt;/code&gt; before starting the goroutines.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;the-revised-code&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/s3-upload-memory#the-revised-code&quot;&gt;The revised code&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;Add the &lt;code&gt;io&lt;/code&gt; and &lt;code&gt;os&lt;/code&gt; imports for the temporary-file approach below.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (u *Uploader) uploadZipFile(ctx context.Context, file *zip.File) error {
  zf, err := file.Open()
  if err != nil {
    return err
  }
  defer zf.Close()

  // NOTE: this is safe to call from multiple goroutines (see godoc).
  temp, err := os.CreateTemp(&quot;&quot;, &quot;s3-upload-*&quot;)
  if err != nil {
    return fmt.Errorf(&quot;creating temp file: %v&quot;, err)
  }
  defer func() {
    _ = temp.Close()
    if err := os.Remove(temp.Name()); err != nil {
      // Log the error.
    }
  }()

  if _, err := io.Copy(temp, zf); err != nil {
    return fmt.Errorf(&quot;writing to temp file: %v&quot;, err)
  }

  // Rewind the file pointer to read again from the temp file on upload.
  if _, err := temp.Seek(0, io.SeekStart); err != nil {
    return fmt.Errorf(&quot;rewinding temp file pointer: %v&quot;, err)
  }

  mimeType := detectMimeType(file.Name)
  _, err = u.uploader.Upload(ctx, &amp;amp;s3.PutObjectInput{
    Bucket:      aws.String(u.bucket),
    Key:         aws.String(file.Name),
    Body:        temp,
    ContentType: aws.String(mimeType),
  })
  return err
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/s3-upload-memory#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aws/aws-sdk-go-v2/issues/2694&quot;&gt;GitHub issue #2694&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/sdk-utilities-s3.html#putobjectinput-body-field-ioreadseeker-vs-ioreader&quot;&gt;PutObjectInput Body Field&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 id=&quot;footnote-label&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/s3-upload-memory#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;The AWS SDK calls this combined interface &lt;a href=&quot;https://github.com/aws/aws-sdk-go-v2/blob/feature/s3/manager/v1.17.25/feature/s3/manager/upload.go#L814-L817&quot;&gt;&lt;code&gt;readSeekerAt&lt;/code&gt;&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/go/s3-upload-memory#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>Benchmarking</title><link>https://dans.land/garden/go/benchmarking</link><guid isPermaLink="true">https://dans.land/garden/go/benchmarking</guid><description>How to write benchmarks in Go.</description><pubDate>Sat, 24 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Go &lt;a href=&quot;https://pkg.go.dev/testing&quot;&gt;testing&lt;/a&gt; package supports &lt;a href=&quot;https://pkg.go.dev/testing#hdr-Benchmarks&quot;&gt;benchmarks&lt;/a&gt; to test the performance of code.&lt;/p&gt;
&lt;p&gt;Test functions look like this:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func TestSomething(t *testing.T)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Benchmark functions look like this:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func BenchmarkSomething(b *testing.B)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#how-it-works&quot;&gt;How it works&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Use &lt;code&gt;b.Loop()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Go 1.24 added &lt;a href=&quot;https://go.dev/blog/testing-b-loop&quot;&gt;&lt;code&gt;b.Loop()&lt;/code&gt;&lt;/a&gt;.
The &lt;code&gt;b.N&lt;/code&gt; examples in this post still work, but have some &lt;a href=&quot;https://dans.land/garden/go/benchmarking#gotchas&quot;&gt;gotchas&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A benchmark has some “code under test”:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func BenchmarkSomething(b *testing.B) {
  for range b.N {
    // Code under test.
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The code under test in the benchmark will be executed &lt;code&gt;b.N&lt;/code&gt; times,
where Go automatically adjusts the value of &lt;code&gt;b.N&lt;/code&gt; until the benchmark lasts long enough to be timed reliably.&lt;/p&gt;
&lt;h2 id=&quot;running-benchmarks&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#running-benchmarks&quot;&gt;Running benchmarks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like test functions, benchmark functions live in &lt;code&gt;*_test.go&lt;/code&gt; files and are run via the Go &lt;a href=&quot;https://pkg.go.dev/cmd/go#hdr-Test_packages&quot;&gt;test command&lt;/a&gt;.
But to run benchmarks, the &lt;code&gt;-bench&lt;/code&gt; &lt;a href=&quot;https://pkg.go.dev/cmd/go#hdr-Testing_flags&quot;&gt;test flag&lt;/a&gt; must be provided.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;# Run all benchmarks
go test ./... -bench .

# Run a specific benchmark
go test ./... -bench BenchmarkSomething&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;By default tests &lt;em&gt;also&lt;/em&gt; run when running benchmarks.
To prevent this use the &lt;code&gt;-run ^$&lt;/code&gt; test flag to run “no tests”.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go test ./... -run ^$ -bench BenchmarkSomething&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;About Go test modes&lt;/p&gt;
&lt;p&gt;Go tests can be run in two different “modes”:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Directory mode&lt;/strong&gt; is in effect when &lt;code&gt;go test&lt;/code&gt; is run without package arguments (e.g. &lt;code&gt;go test&lt;/code&gt;). Here Go compiles source and test files in the current directory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Package list mode&lt;/strong&gt; is in effect when &lt;code&gt;go test&lt;/code&gt; is run with package arguments (e.g. &lt;code&gt;go test ./...&lt;/code&gt;). Here Go compiles source and test files for the listed packages.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Only in mode 2 will Go cache successful package test results to avoid running tests unnecessarily in repeated tests.
When tests are cached, &lt;code&gt;go test&lt;/code&gt; prints &lt;code&gt;(cached)&lt;/code&gt; instead of the elapsed time in the summary.
To disable caching use the flag &lt;code&gt;-count=1&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;controlling-count&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#controlling-count&quot;&gt;Controlling count&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By default a benchmark runs once.
But this can be controlled with the &lt;code&gt;-count&lt;/code&gt; test flag.
It can be useful to run a benchmark multiple times to (better) verify it produces consistent results.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go test ./... -run ^$ -bench BenchmarkSomething -count 10&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Alternatively, the actual number of iterations can be controlled by using the syntax &lt;code&gt;Nx&lt;/code&gt;.
For example, this runs the benchmark for exactly 100 iterations:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go test ./... -run ^$ -bench BenchmarkSomething -benchtime 100x&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;controlling-duration&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#controlling-duration&quot;&gt;Controlling duration&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By default, Go runs enough iterations of each benchmark to take about 1 second.
But this may not be enough to produce a good enough sample size.&lt;/p&gt;
&lt;p&gt;To increase benchmark duration, use the &lt;code&gt;-benchtime&lt;/code&gt; test flag.
For a duration such as &lt;code&gt;10s&lt;/code&gt;, Go runs enough iterations of each benchmark to take the requested time.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go test ./... -run ^$ -bench BenchmarkSomething -benchtime 10s&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;how-to-read-benchmark-results&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#how-to-read-benchmark-results&quot;&gt;How to read benchmark results&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Benchmark results have the following format:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;lt;name&amp;gt; &amp;lt;iterations&amp;gt; &amp;lt;value&amp;gt; &amp;lt;unit&amp;gt; [&amp;lt;value&amp;gt; &amp;lt;unit&amp;gt;...]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;BenchmarkGetClientEncoding-12  26514762  46.58 ns/op
BenchmarkGetClientEncoding-12  25236223  45.42 ns/op
BenchmarkGetClientEncoding-12  24113084  46.17 ns/op&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;Column 1 shows the benchmark name, which always begins with &lt;code&gt;Benchmark&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Column 2 shows the total number of iterations run during the benchmark.&lt;/li&gt;
&lt;li&gt;Column 3 shows the measured value. For example, &lt;code&gt;ns/op&lt;/code&gt; indicates the average amount of time in nanoseconds it took one iteration to complete.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;comparing-benchmark-results&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#comparing-benchmark-results&quot;&gt;Comparing benchmark results&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://pkg.go.dev/golang.org/x/perf/cmd/benchstat&quot;&gt;benchstat&lt;/a&gt; command can be used to compare multiple benchmark results.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Keep in mind&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each benchmark should be run at least 10 times to gather a statistically significant sample of results.
&lt;ul&gt;
&lt;li&gt;Pick a number of benchmark runs (at least 10, ideally 20) and stick to it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Reducing noise and/or increasing the number of benchmark runs makes &lt;code&gt;benchstat&lt;/code&gt; see smaller changes as “statistically significant”.
&lt;ul&gt;
&lt;li&gt;To reduce noise, run benchmarks on an idle machine (i.e. close apps) and connect to a power source.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;First install the command with:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go install golang.org/x/perf/cmd/benchstat@latest&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Then save benchmark results to a text file. For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go test ./... -run ^$ -bench BenchmarkSomething -count 10 | tee v1.txt

go test ./... -run ^$ -bench BenchmarkSomethingV2 -count 10 | tee v2.txt&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;And compare them:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;benchstat v1.txt v2.txt&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;how-to-read-comparison-results&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#how-to-read-comparison-results&quot;&gt;How to read comparison results&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This example output:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;                      │   v1.txt    │               v2.txt                │
                      │   sec/op    │   sec/op     vs base                │
BenchmarkSomething      1.718µ ± 1%   1.423µ ± 1%  -17.20% (p=0.000 n=10)
BenchmarkSomethingV2    3.066µ ± 0%   3.070µ ± 2%        ~ (p=0.446 n=10)
geomean                 2.295µ        2.090µ        -8.94%&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Can be interpreted as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;±&lt;/code&gt; percentage indicates “variation”. The lower, the better: a high variation means unreliable samples and that the benchmark needs to be re-run.&lt;/li&gt;
&lt;li&gt;A negative percentage (&lt;code&gt;-17.20%&lt;/code&gt;) means a benchmark was faster. A positive percentage means slower.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p=&lt;/code&gt; value measures how likely the differences were due to random chance.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~&lt;/code&gt; means there was no statistically significant difference between the two inputs.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;geomean&lt;/code&gt; shows the geometric mean of each column.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;profiling-benchmarks&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#profiling-benchmarks&quot;&gt;Profiling benchmarks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Memory allocations can be printed in the results by providing the &lt;code&gt;-benchmem&lt;/code&gt; test flag.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go test ./... -run ^$ -bench BenchmarkSomething -benchmem&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;But it’s also possible to produce &lt;code&gt;pprof&lt;/code&gt; compatible profiles.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go test ./... -run ^$ -bench BenchmarkSomething -cpuprofile cpu.prof

go test ./... -run ^$ -bench BenchmarkSomething -memprofile mem.prof&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The output &lt;code&gt;.prof&lt;/code&gt; file can then be used to generate a report with &lt;code&gt;go tool pprof&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;tips&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#tips&quot;&gt;Tips&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;benchmark-with-multiple-inputs&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#benchmark-with-multiple-inputs&quot;&gt;Benchmark with multiple inputs&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Like with regular test functions, you can use table-driven benchmarks and sub-benchmarks by invoking &lt;a href=&quot;https://pkg.go.dev/testing#B.Run&quot;&gt;b.Run(name, f)&lt;/a&gt;.
Each &lt;code&gt;b.Run&lt;/code&gt; call creates and runs a separate benchmark.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func BenchmarkSomething(b *testing.B) {
    benchmarks := []struct{
        name  string
        input int
    }{
        {&quot;One&quot;, 1},
        {&quot;Two&quot;, 2},
    }
    for _, bm := range benchmarks {
        b.Run(bm.name, func(b *testing.B) {
            for range b.N {
                testSomething(bm.input)
            }
        })
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To only run certain sub-benchmarks, provide a &lt;code&gt;/&lt;/code&gt; separated list of benchmark and sub-benchmark names to the &lt;code&gt;-bench&lt;/code&gt; test flag.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go test ./... -run ^$ -bench BenchmarkSomething/Two&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;gotchas&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#gotchas&quot;&gt;Gotchas&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Use &lt;code&gt;b.Loop()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Go 1.24 added &lt;a href=&quot;https://go.dev/blog/testing-b-loop&quot;&gt;&lt;code&gt;b.Loop()&lt;/code&gt;&lt;/a&gt; which:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatically excludes setup and cleanup code from benchmark timing.&lt;/li&gt;
&lt;li&gt;Prevents the compiler from optimizing away function calls inside the benchmark loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;1-control-the-timer-when-doing-setup-and-cleanup&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#1-control-the-timer-when-doing-setup-and-cleanup&quot;&gt;1. Control the timer when doing setup and cleanup&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By default the &lt;em&gt;entire&lt;/em&gt; run time of a benchmark function is measured.
Go executes the benchmark many times, and divides total execution time by &lt;code&gt;b.N&lt;/code&gt;.
This means that doing some sort of (expensive) setup and cleanup can affect benchmark results.&lt;/p&gt;
&lt;p&gt;To prevent misleading benchmark results, the timer can be controlled with the following functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pkg.go.dev/testing#B.StopTimer&quot;&gt;b.StopTimer()&lt;/a&gt; and &lt;a href=&quot;https://pkg.go.dev/testing#B.StartTimer&quot;&gt;b.StartTimer()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pkg.go.dev/testing#B.ResetTimer&quot;&gt;b.ResetTimer()&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func BenchmarkSomething(b *testing.B) {
  // Do some (expensive) setup here.
  b.ResetTimer()

  for range b.N {
    // Code under test.
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;2-compiler-optimizations&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#2-compiler-optimizations&quot;&gt;2. Compiler optimizations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It may happen that the compiler optimizes code under test in a benchmark.
When this happens, the benchmark will seem faster than it really is.&lt;/p&gt;
&lt;p&gt;This may happen with non-changing function inputs and unused values.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func isTrueOrFalse(n int) bool {
  if n == 0 {
    return false
  }

  return true
}

func BenchmarkWrong(b *testing.B) {
  for range b.N {
    isTrueOrFalse(0)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Use an input that varies between iterations and keep the result observable:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;var sink bool

func BenchmarkOkay(b *testing.B) {
  for i := 0; i &amp;lt; b.N; i++ {
    sink = isTrueOrFalse(i)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pkg.go.dev/runtime#KeepAlive&quot;&gt;&lt;code&gt;runtime.KeepAlive()&lt;/code&gt;&lt;/a&gt; is for keeping an object reachable until a point in the program,
usually so a finalizer does not run too early.
It does not prevent constant folding or make an otherwise unused calculation observable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/benchmarking#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go/&quot;&gt;How to write benchmarks in Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eli.thegreenplace.net/2023/common-pitfalls-in-go-benchmarking/&quot;&gt;Common pitfalls in Go benchmarking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://go.dev/blog/subtests/&quot;&gt;Using Subtests and Sub-benchmarks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://go.googlesource.com/proposal/+/master/design/14313-benchmark-format.md&quot;&gt;Proposal: Go Benchmark Data Format&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lemire.me/blog/2024/03/17/measuring-your-systems-performance-using-software-go-edition/&quot;&gt;Measuring your system’s performance using software (Go edition)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Building proxies</title><link>https://dans.land/garden/go/building-proxies</link><guid isPermaLink="true">https://dans.land/garden/go/building-proxies</guid><description>What I learned so far (and some musings) about building proxies in Go.</description><pubDate>Fri, 23 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s pretty easy to start building a &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/computer-networks/proxies&quot; data-garden-link=&quot;&quot;&gt;proxy&lt;/a&gt; in Go.
The simplest example of creating a (reverse) proxy looks like this:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;proxy := httputil.NewSingleHostReverseProxy(targetURL)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;But one thing that’s not obvious to me yet is the best way to work with upstreams (i.e. targets to proxy to) that are not known beforehand.&lt;/p&gt;
&lt;p&gt;For example, Caddy has support for &lt;a href=&quot;https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#dynamic-upstreams&quot;&gt;dynamic upstreams&lt;/a&gt;.
But it looks like you do need to know them beforehand?&lt;/p&gt;
&lt;p&gt;So I’m not sure yet what the “best practice approach” is to proxy to a different target for different requests (e.g. performance-wise).
But I guess it depends on the exact use-case(s).&lt;/p&gt;
&lt;p&gt;I did learn that you can use &lt;code&gt;httputil.ReverseProxy&lt;/code&gt; and &lt;code&gt;Rewrite&lt;/code&gt; to do something more custom per request:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func NewProxy() *httputil.ReverseProxy {
  return &amp;amp;httputil.ReverseProxy{
    Transport: &amp;amp;http.Transport{
      IdleConnTimeout:     2 * time.Minute,
      MaxIdleConnsPerHost: 32,
      MaxIdleConns:        100,
    },
    Rewrite: func(pr *httputil.ProxyRequest) {
      target, ok := ValidatedTargetFromContext(pr.In.Context())
      if !ok {
        // Leave no outbound target, so the transport fails closed.
        pr.Out.URL = &amp;amp;url.URL{}
        return
      }
      pr.SetURL(target)
      pr.Out.Host = target.Host
    },
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Here &lt;code&gt;ValidatedTargetFromContext&lt;/code&gt; only returns a parsed &lt;code&gt;*url.URL&lt;/code&gt; after checking its scheme and host against an explicit allowlist (or other policy).
Parsing alone is not sufficient; otherwise this can become an SSRF/open-proxy endpoint.&lt;/p&gt;
&lt;p&gt;In this example, the validated target is passed through the request context (&lt;code&gt;ValidatedTargetFromContext&lt;/code&gt;).
But this doesn’t feel great (and I haven’t explored what performance looks like when using this yet).&lt;/p&gt;
&lt;p&gt;Maybe it’s better to implement a “non-standard” (i.e. not using &lt;code&gt;ServeHTTP&lt;/code&gt;) &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/go/http-handlers&quot; data-garden-link=&quot;&quot;&gt;handler&lt;/a&gt; and just pass extra information to it?&lt;/p&gt;
&lt;p&gt;Something like:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;customProxy.ProxyHTTP(w http.ResponseWriter, r *http.Request, targetURL string)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;Looks like Caddy also started from &lt;code&gt;httputil.ReverseProxy&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/caddyserver/caddy/blob/56e3a88efe39be6e380496778e7b94cb97f60c00/modules/caddyhttp/reverseproxy/reverseproxy.go#L989-L990&quot;&gt;https://github.com/caddyserver/caddy/blob/56e3a88efe39be6e380496778e7b94cb97f60c00/modules/caddyhttp/reverseproxy/reverseproxy.go#L989-L990&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/building-proxies#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eli.thegreenplace.net/2022/go-and-proxy-servers-part-1-http-proxies/&quot;&gt;Go and Proxy Servers: Part 1 - HTTP Proxies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eli.thegreenplace.net/2022/go-and-proxy-servers-part-2-https-proxies/&quot;&gt;Go and Proxy Servers: Part 2 - HTTPS Proxies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eli.thegreenplace.net/2022/go-and-proxy-servers-part-3-socks-proxies/&quot;&gt;Go and Proxy Servers: Part 3 - SOCKS proxies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Comments</title><link>https://dans.land/garden/go/comments</link><guid isPermaLink="true">https://dans.land/garden/go/comments</guid><description>Learning new things about Go doc comments.</description><pubDate>Mon, 19 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Go doc comments are comments directly above &lt;code&gt;package&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt;, &lt;code&gt;var&lt;/code&gt; and &lt;code&gt;func&lt;/code&gt; declarations.
Every exported name should have a doc comment.&lt;/p&gt;
&lt;p&gt;The Go tooling can extract documentation from the source code when doc comments are used correctly.&lt;/p&gt;
&lt;p&gt;But it turns out I wasn’t always formatting doc comments correctly,
and I didn’t know you could use &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/go/comments#links&quot;&gt;links&lt;/a&gt; and &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/go/comments#doc-links&quot;&gt;doc links&lt;/a&gt; (which also work in your favourite code editor).&lt;/p&gt;
&lt;h2 id=&quot;paragraphs&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/comments#paragraphs&quot;&gt;Paragraphs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Line breaks are preserved by &lt;code&gt;gofmt&lt;/code&gt; in paragraphs.
So comments will not be rewrapped (&lt;code&gt;godoc&lt;/code&gt; and &lt;code&gt;pkgsite&lt;/code&gt; &lt;em&gt;will&lt;/em&gt; rewrap comment text when printing it).&lt;/p&gt;
&lt;p&gt;This makes it possible to use semantic linefeeds:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This is a paragraph.
// Each sentence can be placed on a separate line.
// It won&apos;t be rewrapped by gofmt.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Semantic linefeeds?&lt;/p&gt;
&lt;p&gt;Semantic linefeeds place sentences or long phrases on a separate line.
This can make text easier to read, and diffs easier to maintain as code and comments change over time.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://rhodesmill.org/brandon/2012/one-sentence-per-line/&quot;&gt;semantic linefeeds&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;headings&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/comments#headings&quot;&gt;Headings&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Headings are lines that begin with &lt;code&gt;#&lt;/code&gt; followed by a space and text.
Headings must be unindented and followed by a blank line:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// # This is a heading
//
// This is a paragraph.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;links&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/comments#links&quot;&gt;Links&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Link targets are created by using the format &lt;code&gt;[Text]: URL&lt;/code&gt;.
Then other text in the same comment block can refer to the link by using &lt;code&gt;[Text]&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This is a paragraph using a [Link to example].
//
// [Link to example]: https://example.com&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;doc-links&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/comments#doc-links&quot;&gt;Doc links&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Doc links link to symbols in the current or an external package.
They are created using the format &lt;code&gt;[Name]&lt;/code&gt; and &lt;code&gt;[pkg.Name]&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This links to [http.Handler] and [SomeStruct].&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;It’s also possible to link to fields using &lt;code&gt;[Name.FieldName]&lt;/code&gt; and methods using &lt;code&gt;[Name.FuncName]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pointer types can also be linked by using a leading &lt;code&gt;*&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This links to a pointer [*bytes.Buffer].&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;code-blocks&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/comments#code-blocks&quot;&gt;Code blocks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Code blocks are indented lines that are not a &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/go/comments#lists&quot;&gt;list&lt;/a&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This is a code block:
//
//  func Hello() string {
//    return &quot;Hello&quot;
//  }&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;lists&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/comments#lists&quot;&gt;Lists&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Lists are indented lines that begin with a decimal number and/or special character.&lt;/p&gt;
&lt;h3 id=&quot;numbered-lists&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/comments#numbered-lists&quot;&gt;Numbered lists&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For numbered lists, a line must:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Begin with a decimal number.&lt;/li&gt;
&lt;li&gt;Followed by a special character: period &lt;code&gt;.&lt;/code&gt; or right parenthesis &lt;code&gt;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Followed by a space or tab.&lt;/li&gt;
&lt;li&gt;Followed by text.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This is a numbered list:
//  1. One
//  2. Two&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;bullet-lists&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/comments#bullet-lists&quot;&gt;Bullet lists&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For bullet lists, a line must:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Begin with a special character: star &lt;code&gt;*&lt;/code&gt;, plus &lt;code&gt;+&lt;/code&gt;, dash &lt;code&gt;-&lt;/code&gt; or Unicode bullet &lt;code&gt;•&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Followed by a space or tab.&lt;/li&gt;
&lt;li&gt;Followed by text.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// This is a bullet list:
//  - One
//  - Two&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/comments#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tip.golang.org/doc/comment&quot;&gt;Go Doc Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Nagle&apos;s algorithm</title><link>https://dans.land/garden/computer-networks/nagles-algorithm</link><guid isPermaLink="true">https://dans.land/garden/computer-networks/nagles-algorithm</guid><description>Nagle&apos;s algorithm solves the small-packet problem, but can cause problems.</description><pubDate>Sun, 18 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sending small packets has a large overhead that can cause network congestion.&lt;/p&gt;
&lt;p&gt;For example, sending 1 byte of data over the network results in a 41-byte packet (20 bytes for TCP and 20 bytes for IPv4 headers).&lt;/p&gt;
&lt;p&gt;This is called the “small-packet problem” and for example happens in telnet sessions, where every single character is sent over the network as it’s typed.&lt;/p&gt;
&lt;p&gt;Especially over a slow network, sending many small packets like that can lead to network congestion.&lt;/p&gt;
&lt;h2 id=&quot;solving-the-small-packet-problem&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/nagles-algorithm#solving-the-small-packet-problem&quot;&gt;Solving the small-packet problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nagle’s algorithm solves the small-packet problem by essentially delaying (and buffering) the sending of packets to improve bandwidth efficiency and throughput.&lt;/p&gt;
&lt;p&gt;The algorithm can be described as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If the sender has unacknowledged data, buffer new application data.&lt;/li&gt;
&lt;li&gt;Send it when the outstanding data is acknowledged, or when enough data is available for a full-sized segment.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is sometimes called “nagling” and is usually &lt;em&gt;enabled&lt;/em&gt; by default.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;Nagle’s algorithm is controlled via &lt;code&gt;TCP_NODELAY&lt;/code&gt;.
Enabling &lt;code&gt;TCP_NODELAY&lt;/code&gt; &lt;em&gt;disables&lt;/em&gt; Nagle’s algorithm.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;delayed-acks&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/nagles-algorithm#delayed-acks&quot;&gt;Delayed ACKs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A different solution for the same problem is to use delayed acknowledgments (delayed ACKs).&lt;/p&gt;
&lt;p&gt;Delayed ACKs combine several ACK responses into a single one, by waiting for a short period that varies by implementation (e.g. 200 ms).&lt;/p&gt;
&lt;p&gt;This way it can either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Combine multiple ACKs.&lt;/li&gt;
&lt;li&gt;Include the ACK in data it needs to send anyway (this is called “piggybacking”).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Delayed ACKs are usually &lt;em&gt;also&lt;/em&gt; enabled by default.&lt;/p&gt;
&lt;h2 id=&quot;nagles-algorithm-and-delayed-acks&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/nagles-algorithm#nagles-algorithm-and-delayed-acks&quot;&gt;Nagle’s algorithm and delayed ACKs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nagle’s algorithm can interact badly with delayed ACKs.
When both are enabled, a small write can wait for an ACK or for enough data to fill a segment, while the ACK itself is delayed.&lt;/p&gt;
&lt;p&gt;This becomes problematic for latency-sensitive applications.&lt;/p&gt;
&lt;blockquote cite=&quot;https://news.ycombinator.com/item?id=34180239&quot;&gt;
&lt;p&gt;… after I put in Nagle’s algorithm, Berkeley put in delayed ACKs.
Delayed ACKs delay sending an empty ACK packet for a short, fixed period based on human typing speed, maybe 100ms.
This was a hack Berkeley put in to handle large numbers of dumb terminals going in to time-sharing computers using terminal to Ethernet concentrators.
Without delayed ACKs, each keystroke sent a datagram with one payload byte, and got a datagram back with no payload, just an ACK, followed shortly thereafter by a datagram with one echoed character.
So they got a 30% load reduction for their TELNET application.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=34180239&quot;&gt;https://news.ycombinator.com/item?id=34180239&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;h2 id=&quot;disabling-nagles-algorithm&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/nagles-algorithm#disabling-nagles-algorithm&quot;&gt;Disabling Nagle’s algorithm&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For some modern latency-sensitive applications, Nagle’s algorithm should be disabled.
But it depends on the write pattern and latency requirements if it should be disabled or not:
batching can improve efficiency, while small interactive writes may benefit from sending data immediately.&lt;/p&gt;
&lt;p&gt;So disabling Nagle can reduce latency, but can also increase the number of small packets.&lt;/p&gt;
&lt;p&gt;For example, Go considers disabling Nagle’s algorithm to be a sane default: &lt;a href=&quot;https://pkg.go.dev/net#TCPConn.SetNoDelay&quot;&gt;pkg.go.dev/net#TCPConn.SetNoDelay&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/nagles-algorithm#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://brooker.co.za/blog/2024/05/09/nagle.html&quot;&gt;It’s always TCP_NODELAY. Every damn time.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jvns.ca/blog/2015/11/21/why-you-should-understand-a-little-about-tcp/&quot;&gt;Why you should understand (a little) about TCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=34179426&quot;&gt;Golang disables Nagle’s Algorithm by default&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Proxies</title><link>https://dans.land/garden/computer-networks/proxies</link><guid isPermaLink="true">https://dans.land/garden/computer-networks/proxies</guid><description>What I learned about proxies so far.</description><pubDate>Sun, 18 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A proxy is an “intermediary” between a client making an HTTP request and a server responding to that request.&lt;/p&gt;
&lt;p&gt;Generally speaking there are 2 types of proxies:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Forward proxies&lt;/strong&gt; sit between the client and “the internet”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reverse proxies&lt;/strong&gt; sit between “the internet” and a server.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/proxies#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling&quot;&gt;MDN: Proxy servers and tunneling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.cloudflare.com/a-primer-on-proxies/&quot;&gt;Cloudflare: A Primer on Proxies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>XFF</title><link>https://dans.land/garden/computer-networks/xff</link><guid isPermaLink="true">https://dans.land/garden/computer-networks/xff</guid><description>The X-Forwarded-For (XFF) header contains the IP address of a client connecting via a proxy.</description><pubDate>Sun, 18 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The XFF HTTP request header is a &lt;strong&gt;de facto standard&lt;/strong&gt; header that contains the IP address of a client that connects to a server via a &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/computer-networks/proxies&quot; data-garden-link=&quot;&quot;&gt;proxy server&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When a client makes a request to a server, the server observes the source IP address of the connection.
But when a proxy sits in between,
the server sees the proxy’s address as its connection peer,
where the proxy may preserve the original client address in this header.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;The standardized version of &lt;code&gt;X-Forwarded-For&lt;/code&gt; is the &lt;code&gt;Forwarded&lt;/code&gt; HTTP request header.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded&quot;&gt;developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;format&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/xff#format&quot;&gt;Format&lt;/a&gt;&lt;/h2&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;X-Forwarded-For: &amp;lt;client&amp;gt;, &amp;lt;proxy1&amp;gt;, &amp;lt;proxy2&amp;gt;, ...&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;In the common append model, each proxy adds the address of the peer from which it received the request.
The first value is the original client’s address, assuming the chain is well behaved.&lt;/p&gt;
&lt;p&gt;So the rightmost XFF value is the address observed by the final proxy.
The final proxy’s own address is the server’s connection peer.
With just one proxy between the client and server, the header contains the client’s address.&lt;/p&gt;
&lt;h3 id=&quot;parsing-multiple-xff-headers&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/xff#parsing-multiple-xff-headers&quot;&gt;Parsing multiple XFF headers&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There can be &lt;em&gt;multiple&lt;/em&gt; XFF headers present in a request.&lt;/p&gt;
&lt;p&gt;When this happens, the IP addresses in &lt;em&gt;all&lt;/em&gt; headers must be treated as a &lt;em&gt;single&lt;/em&gt; list:
starting with the first IP address of the first header and continuing to the last IP address of the last header.&lt;/p&gt;
&lt;p&gt;Essentially, you must concatenate all XFF header values.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Warning&lt;/p&gt;
&lt;p&gt;It is insufficient to only use one XFF header when multiple are present.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;security&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/xff#security&quot;&gt;Security&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The XFF header is untrustworthy unless the request arrived through a verified trusted proxy and the proxy is configured to append or overwrite the header correctly.
Any requests that reach the origin directly must be treated as untrusted, regardless of their XFF header.
Restricting origin access to your proxies helps enforce this boundary.
Leftmost untrusted values must only be used when there’s no risk of using potentially “spoofed” values.&lt;/p&gt;
&lt;p&gt;Start with the connection peer and walk the addresses from right to left, skipping proxies in your trusted list.
Use the first untrusted address for security-related uses, like rate-limiting or blocking requests.
A trusted hop count can also work, but only when every permitted path has the expected number of proxies.&lt;/p&gt;
&lt;h2 id=&quot;privacy&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/xff#privacy&quot;&gt;Privacy&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Because the XFF header exposes privacy-sensitive information (the IP address of a client), the user’s privacy must be kept in mind when using this header.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/xff#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For&quot;&gt;MDN: X-Forwarded-For&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>PGO</title><link>https://dans.land/garden/go/pgo</link><guid isPermaLink="true">https://dans.land/garden/go/pgo</guid><description>Profile-guided optimization (PGO) in Go.</description><pubDate>Sat, 30 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When Go builds a binary, the compiler will optimize it by default (e.g. by inlining code).
But here the compiler will make a “best effort guess” by using static heuristics based on (un)common paths in functions.&lt;/p&gt;
&lt;p&gt;Starting from Go &lt;code&gt;1.20&lt;/code&gt;, the compiler supports profile-guided optimization (PGO) to better optimize built binaries by using collected CPU pprof profiles.
Go 1.21 made PGO production-ready and enabled it by default when a profile is present.&lt;/p&gt;
&lt;p&gt;Providing profiles to the compiler gives it more information about how code behaves in a “real” production environment,
and it can better optimize the built binary.
For example, by more aggressively optimizing the most frequently used functions, or by more accurately selecting common cases.&lt;/p&gt;
&lt;h2 id=&quot;setting-expectations&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pgo#setting-expectations&quot;&gt;Setting expectations&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote cite=&quot;https://go.dev/blog/pgo&quot;&gt;
&lt;p&gt;In Go 1.21, workloads typically get between 2% and 7% CPU usage improvements from enabling PGO.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://go.dev/blog/pgo&quot;&gt;https://go.dev/blog/pgo&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;blockquote cite=&quot;https://go.dev/doc/pgo&quot;&gt;
&lt;p&gt;As of Go 1.22, benchmarks for a representative set of Go programs show that building with PGO improves performance by around 2-14%.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://go.dev/doc/pgo&quot;&gt;https://go.dev/doc/pgo&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;blockquote cite=&quot;https://go.dev/doc/go1.23#compiler&quot;&gt;
&lt;p&gt;For 386 and amd64, the compiler will use information from PGO to align certain hot blocks in loops.
This improves performance an additional 1-1.5% at a cost of an additional 0.1% text and binary size.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://go.dev/doc/go1.23#compiler&quot;&gt;https://go.dev/doc/go1.23#compiler&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;p&gt;The Go team expects performance gains to keep increasing over time as more optimizations take advantage of PGO in future Go versions.&lt;/p&gt;
&lt;h2 id=&quot;how-to-use-pgo&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pgo#how-to-use-pgo&quot;&gt;How to use PGO&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Add a profile named &lt;code&gt;default.pgo&lt;/code&gt; in the main package directory.&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;-pgo&lt;/code&gt; flag to provide the path to a profile when using &lt;code&gt;go build&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;combining-profiles&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pgo#combining-profiles&quot;&gt;Combining profiles&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To use a more representative profile for PGO, it’s possible to combine multiple profiles:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;go tool pprof -proto a.prof b.prof c.prof &amp;gt; combined.prof&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/pgo#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://theyahya.com/posts/go-pgo/&quot;&gt;A Deep Look Into Golang Profile-Guided Optimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.dolthub.com/blog/2024-02-02-profile-guided-optimization/&quot;&gt;Testing out Profile-Guided Optimization on Dolt’s SQL Benchmarks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://andrewwphillips.github.io/blog/pgo.html&quot;&gt;PGO&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>SQLite CLI</title><link>https://dans.land/garden/sqlite-cli</link><guid isPermaLink="true">https://dans.land/garden/sqlite-cli</guid><description>Learning about the SQLite command-line interface (CLI).</description><pubDate>Sat, 15 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://www.sqlite.org/index.html&quot;&gt;SQLite&lt;/a&gt; provides a &lt;a href=&quot;https://www.sqlite.org/cli.html&quot;&gt;command-line interface&lt;/a&gt; (CLI) program named &lt;code&gt;sqlite3&lt;/code&gt;.
It is often already installed, or easy to install, on common operating systems.&lt;/p&gt;
&lt;h2 id=&quot;basic-usage&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#basic-usage&quot;&gt;Basic usage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The CLI can be run with or without command line options (flags).&lt;/p&gt;
&lt;p&gt;When a flag is provided, it must be prefixed with &lt;code&gt;-&lt;/code&gt; or &lt;code&gt;--&lt;/code&gt;.
For example, &lt;code&gt;-version&lt;/code&gt; and &lt;code&gt;--version&lt;/code&gt; do the same thing:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite3 -version&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;When &lt;code&gt;sqlite3&lt;/code&gt; is run without flags, it will connect to a temporary in-memory database (which will be deleted on exit) in &lt;strong&gt;interactive mode&lt;/strong&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite3

SQLite version 3.37.0 2021-12-09 01:34:53
Enter &quot;.help&quot; for usage hints.
Connected to a transient in-memory database.
Use &quot;.open FILENAME&quot; to reopen on a persistent database.

sqlite&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;When in interactive mode, the prompt is &lt;code&gt;sqlite&amp;gt;&lt;/code&gt; and it reads text input from the keyboard:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL statements.&lt;/li&gt;
&lt;li&gt;Dot commands like &lt;code&gt;.open&lt;/code&gt; (where some dot commands also accept flags).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But it’s also possible to redirect &lt;code&gt;sqlite3&lt;/code&gt; I/O (input/output) to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/sqlite-cli#read-sql-statements-from-a-file&quot;&gt;Read SQL statements from a file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/sqlite-cli#write-results-to-a-file&quot;&gt;Write results to a file&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;help&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#help&quot;&gt;Help&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To see how to use the CLI (and print all available CLI flags):&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite3 -help&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To print all available dot commands (in interactive mode):&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .help&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To see how to use a dot command (in interactive mode), and print available dot command flags, run &lt;code&gt;.help DOT_COMMAND&lt;/code&gt;.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .help .import&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;open-a-database&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#open-a-database&quot;&gt;Open a database&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When a filename is provided to the &lt;code&gt;sqlite3&lt;/code&gt; command, it will either create a new database or open an existing database in interactive mode:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite3 mydb&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;In interactive mode, a connection to a new or existing database can always be created via the &lt;code&gt;.open&lt;/code&gt; dot command.
To connect to a temporary in-memory database, use &lt;code&gt;:memory:&lt;/code&gt; as the database file name.&lt;/p&gt;
&lt;p&gt;To destroy any data in an existing database run &lt;code&gt;.open -new FILENAME&lt;/code&gt;.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .open -new existingdb&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To open a database in read-only mode use the &lt;code&gt;-readonly&lt;/code&gt; flag:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite3 -readonly mydb&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This also works in interactive mode:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .open -readonly myotherdb&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;databases-and-schemas&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#databases-and-schemas&quot;&gt;Databases and schemas&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To see all databases in interactive mode:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .databases&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To see all tables (including &lt;a href=&quot;https://www.sqlite.org/lang_attach.html&quot;&gt;attached databases&lt;/a&gt;) in interactive mode:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .tables&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To see all indexes in interactive mode:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .indexes
sqlite&amp;gt; .indexes tablename&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To see the complete schema of the database (including attached databases) in interactive mode:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .schema
sqlite&amp;gt; .schema tablename&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;read-sql-statements-from-a-file&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#read-sql-statements-from-a-file&quot;&gt;Read SQL statements from a file&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In interactive mode the &lt;code&gt;.read&lt;/code&gt; dot command can be used to read SQL statements (and dot commands) from a file:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .read script.sql&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;pipe-input&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#pipe-input&quot;&gt;Pipe input&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If the argument to &lt;code&gt;.read&lt;/code&gt; begins with the pipe symbol (&lt;code&gt;|&lt;/code&gt;),
then instead of opening the argument as a file, it runs the argument as a command and uses the output of that command as its input.
This can be useful to run scripts that generate SQL.&lt;/p&gt;
&lt;h2 id=&quot;write-results-to-a-file&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#write-results-to-a-file&quot;&gt;Write results to a file&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default &lt;code&gt;sqlite3&lt;/code&gt; sends all output to “standard output”,
but this can be changed via the &lt;code&gt;.output&lt;/code&gt; and &lt;code&gt;.once&lt;/code&gt; dot commands in interactive mode.&lt;/p&gt;
&lt;p&gt;To output &lt;em&gt;all&lt;/em&gt; query results to a file:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .mode list
sqlite&amp;gt; .separator ,
sqlite&amp;gt; .output books_and_authors.txt
sqlite&amp;gt;
sqlite&amp;gt; SELECT * FROM books;
sqlite&amp;gt; SELECT * FROM authors;
sqlite&amp;gt;
sqlite&amp;gt; .exit&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To do the above just once, use the &lt;code&gt;.once&lt;/code&gt; dot command instead.&lt;/p&gt;
&lt;h3 id=&quot;pipe-results&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#pipe-results&quot;&gt;Pipe results&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If the argument to &lt;code&gt;.output&lt;/code&gt; or &lt;code&gt;.once&lt;/code&gt; begins with the pipe symbol (&lt;code&gt;|&lt;/code&gt;),
then it runs the argument as a command and the output is sent to that command.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .once | open -f
sqlite&amp;gt; SELECT * FROM books;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;load-file-content-into-a-table-column&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#load-file-content-into-a-table-column&quot;&gt;Load file content into a table column&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;readfile()&lt;/code&gt; function loads file content as a &lt;code&gt;BLOB&lt;/code&gt; in interactive mode.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; CREATE TABLE images(
sqlite&amp;gt; name TEXT,
sqlite&amp;gt; type TEXT,
sqlite&amp;gt; img BLOB
sqlite&amp;gt; );
sqlite&amp;gt;
sqlite&amp;gt; INSERT INTO images(name,type,img)
sqlite&amp;gt; VALUES(&apos;icon&apos;,&apos;png&apos;,readfile(&apos;icon.png&apos;));&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;write-a-table-column-to-a-file&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#write-a-table-column-to-a-file&quot;&gt;Write a table column to a file&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;writefile()&lt;/code&gt; function writes a column value to a file in interactive mode.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; SELECT writefile(&apos;icon.png&apos;,img) FROM images WHERE name=&apos;icon&apos;;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;import-csv-into-table&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#import-csv-into-table&quot;&gt;Import CSV into table&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To import a CSV file into a table in interactive mode:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .import -csv file.csv tablename&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To import into a table not part of the “main” database the &lt;code&gt;-schema&lt;/code&gt; flag can be used.
This specifies that the table is part of another “schema” (useful for attached databases or to import into a temporary table).&lt;/p&gt;
&lt;h2 id=&quot;export-results-to-csv&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#export-results-to-csv&quot;&gt;Export results to CSV&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To export results to a CSV file in interactive mode:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite&amp;gt; .headers on
sqlite&amp;gt; .mode csv
sqlite&amp;gt; .once ~/data.csv
sqlite&amp;gt;
sqlite&amp;gt; SELECT * FROM table;
sqlite&amp;gt;
sqlite&amp;gt; .exit&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;dump-and-restore-a-database&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#dump-and-restore-a-database&quot;&gt;Dump and restore a database&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dump (converts entire database content into a single UTF-8 text file):&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite3 mydb .dump | gzip -c &amp;gt; mydb.dump.gz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Restore:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;zcat mydb.dump.gz | sqlite3 mydb&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;configuration&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#configuration&quot;&gt;Configuration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;.sqliterc&lt;/code&gt; resource file can be created in the “home directory” to configure dot command settings.
For example to change the output format for all queries:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;~/.sqliterc&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;.mode box&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;After creating the &lt;code&gt;.sqliterc&lt;/code&gt; file, it will be loaded on startup:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite3 mydb

-- Loading resources from /Users/daniel/.sqliterc
SQLite version 3.37.0 2021-12-09 01:34:53
Enter &quot;.help&quot; for usage hints.

sqlite&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;one-line-commands&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#one-line-commands&quot;&gt;One-line commands&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It’s possible to “bypass” interactive mode and run SQL statements directly when using the &lt;code&gt;sqlite3&lt;/code&gt; command via the last argument:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite3 mydb &quot;SELECT * FROM table;&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;And by using CLI flags like &lt;code&gt;-cmd&lt;/code&gt; it’s possible to shorten certain actions.&lt;/p&gt;
&lt;h3 id=&quot;one-line-import-and-query-csv&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#one-line-import-and-query-csv&quot;&gt;One-line import and query CSV&lt;/a&gt;&lt;/h3&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite3 -csv -cmd &quot;.import ~/data.csv data&quot; :memory: &quot;SELECT * FROM data;&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;one-line-export-results-to-csv&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/sqlite-cli#one-line-export-results-to-csv&quot;&gt;One-line export results to CSV&lt;/a&gt;&lt;/h3&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sqlite3 -csv -header mydb &quot;SELECT * FROM books;&quot; &amp;gt; ~/books.csv&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content:encoded></item><item><title>Caddy local CA</title><link>https://dans.land/garden/computer-networks/caddy-local-ca</link><guid isPermaLink="true">https://dans.land/garden/computer-networks/caddy-local-ca</guid><description>How Firefox trusts Caddy&apos;s local Certificate Authority (CA).</description><pubDate>Fri, 23 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Update&lt;/p&gt;
&lt;p&gt;On Windows, macOS, and Android, Firefox 120+ &lt;a href=&quot;https://support.mozilla.org/en-US/kb/automatically-trust-third-party-certificates&quot;&gt;automatically&lt;/a&gt; trusts third-party root certificates installed in the operating system’s trust store, with this feature enabled by default.
On Linux, this is not enabled by default, so you may still need to import Caddy’s CA manually.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When running &lt;a href=&quot;https://caddyserver.com/&quot;&gt;Caddy&lt;/a&gt; locally, it will also generate its own local Certificate Authority (CA).
Caddy will use this CA to sign certificates for &lt;a href=&quot;https://caddyserver.com/docs/automatic-https#local-https&quot;&gt;local HTTPS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is pretty cool!
When Firefox uses the operating system trust store, Caddy’s local HTTPS works if its CA is trusted there.
If that trust is unavailable, Firefox will show the error code &lt;code&gt;SEC_ERROR_UNKNOWN_ISSUER&lt;/code&gt; when visiting &lt;code&gt;https://localhost&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;SEC_ERROR_UNKNOWN_ISSUER&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1970&quot; height=&quot;1530&quot; src=&quot;https://dans.land/assets/error.D8nS0a_u_ZnjNNU.webp&quot; srcset=&quot;https://dans.land/assets/error.D8nS0a_u_r2Jvx.webp 320w, https://dans.land/assets/error.D8nS0a_u_20QjOu.webp 480w, https://dans.land/assets/error.D8nS0a_u_yMMyk.webp 672w, https://dans.land/assets/error.D8nS0a_u_Z2d6y3k.webp 960w, https://dans.land/assets/error.D8nS0a_u_Z2cXoLJ.webp 1344w, https://dans.land/assets/error.D8nS0a_u_ZnjNNU.webp 1970w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;SEC_ERROR_UNKNOWN_ISSUER&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If Firefox does not recognize the CA from the operating system trust store, you can &lt;a href=&quot;https://support.mozilla.org/en-US/questions/1175296&quot;&gt;manually import&lt;/a&gt; Caddy’s local root certificate into Firefox.&lt;/p&gt;
&lt;h2 id=&quot;how-to-import-caddys-local-root-certificate-into-firefox&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/caddy-local-ca#how-to-import-caddys-local-root-certificate-into-firefox&quot;&gt;How to import Caddy’s local root certificate into Firefox?&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open Firefox and go to &lt;code&gt;about:preferences#privacy&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Scroll down to the &lt;code&gt;Security &amp;gt; Certificates&lt;/code&gt; section and click &lt;code&gt;View Certificates&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Privacy and security settings&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1960&quot; height=&quot;1360&quot; src=&quot;https://dans.land/assets/2-settings.Ch1fnsZS_PcijY.webp&quot; srcset=&quot;https://dans.land/assets/2-settings.Ch1fnsZS_1iCoS0.webp 320w, https://dans.land/assets/2-settings.Ch1fnsZS_Z1kiVfy.webp 480w, https://dans.land/assets/2-settings.Ch1fnsZS_Oxnfs.webp 672w, https://dans.land/assets/2-settings.Ch1fnsZS_2l6Lr6.webp 960w, https://dans.land/assets/2-settings.Ch1fnsZS_25Kuq2.webp 1344w, https://dans.land/assets/2-settings.Ch1fnsZS_PcijY.webp 1960w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Privacy and security settings&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select the &lt;code&gt;Authorities&lt;/code&gt; tab and click &lt;code&gt;Import&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Certificate manager&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1860&quot; height=&quot;1280&quot; src=&quot;https://dans.land/assets/3-import.Ced1wwg4_ZLjhv5.webp&quot; srcset=&quot;https://dans.land/assets/3-import.Ced1wwg4_Z2lm34K.webp 320w, https://dans.land/assets/3-import.Ced1wwg4_Q8nNp.webp 480w, https://dans.land/assets/3-import.Ced1wwg4_ySNBs.webp 672w, https://dans.land/assets/3-import.Ced1wwg4_Z1U7UYr.webp 960w, https://dans.land/assets/3-import.Ced1wwg4_RE8yw.webp 1344w, https://dans.land/assets/3-import.Ced1wwg4_ZLjhv5.webp 1860w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Certificate manager&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find Caddy’s local root certificate in its &lt;a href=&quot;https://caddyserver.com/docs/conventions#data-directory&quot;&gt;data directory&lt;/a&gt; and open it. On a Mac it’s located at &lt;code&gt;~/Library/Application\ Support/Caddy/pki/authorities/local/root.crt&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Caddy local certificate&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1860&quot; height=&quot;1280&quot; src=&quot;https://dans.land/assets/4-open.BEVpBDVz_yeKey.webp&quot; srcset=&quot;https://dans.land/assets/4-open.BEVpBDVz_2iTCQ0.webp 320w, https://dans.land/assets/4-open.BEVpBDVz_C4kph.webp 480w, https://dans.land/assets/4-open.BEVpBDVz_ZUAGqf.webp 672w, https://dans.land/assets/4-open.BEVpBDVz_Z1vaegE.webp 960w, https://dans.land/assets/4-open.BEVpBDVz_Z1gB2gs.webp 1344w, https://dans.land/assets/4-open.BEVpBDVz_yeKey.webp 1860w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Caddy local certificate&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the &lt;code&gt;Trust this CA to identify websites&lt;/code&gt; checkbox and click &lt;code&gt;OK&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Trust new Certificate Authority&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1272&quot; height=&quot;462&quot; src=&quot;https://dans.land/assets/5-trust.eJWwfIwi_2pPHKV.webp&quot; srcset=&quot;https://dans.land/assets/5-trust.eJWwfIwi_Zk4dpN.webp 320w, https://dans.land/assets/5-trust.eJWwfIwi_28J5Bf.webp 480w, https://dans.land/assets/5-trust.eJWwfIwi_Z1wMLeJ.webp 672w, https://dans.land/assets/5-trust.eJWwfIwi_l5rrx.webp 960w, https://dans.land/assets/5-trust.eJWwfIwi_2pPHKV.webp 1272w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Trust new Certificate Authority&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;Caddy Local Authority&lt;/code&gt; should now be listed in the &lt;code&gt;Authorities&lt;/code&gt; tab.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Caddy Local Authority&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1860&quot; height=&quot;1280&quot; src=&quot;https://dans.land/assets/6-imported.CTwrVks3_Z2hW74o.webp&quot; srcset=&quot;https://dans.land/assets/6-imported.CTwrVks3_Zb8GCm.webp 320w, https://dans.land/assets/6-imported.CTwrVks3_ZJ0TEO.webp 480w, https://dans.land/assets/6-imported.CTwrVks3_Z1zhARD.webp 672w, https://dans.land/assets/6-imported.CTwrVks3_21kUp6.webp 960w, https://dans.land/assets/6-imported.CTwrVks3_1222K5.webp 1344w, https://dans.land/assets/6-imported.CTwrVks3_Z2hW74o.webp 1860w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Caddy Local Authority&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart Firefox, and accessing localhost over HTTPS will now work!&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded></item><item><title>Obsidian web clipper</title><link>https://dans.land/garden/obsidian-web-clipper</link><guid isPermaLink="true">https://dans.land/garden/obsidian-web-clipper</guid><description>My bookmarklet to clip web pages to Obsidian.</description><pubDate>Sun, 18 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Warning&lt;/p&gt;
&lt;p&gt;Obsidian now has an official &lt;a href=&quot;https://obsidian.md/clipper&quot;&gt;web clipper&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I recently started using &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt; and I like it a lot!
One thing I was missing, though, was a way to quickly save (i.e. “clip”) a web page to Obsidian from my browser.
So I was happy to find Stephan Ango’s &lt;a href=&quot;https://stephanango.com/obsidian-web-clipper&quot;&gt;Obsidian web clipper&lt;/a&gt; which does just that (thanks Stephan!).&lt;/p&gt;
&lt;p&gt;Stephan’s web clipper works pretty well, but I wanted slightly different behavior.
Since the web clipper is an open source bookmarklet, it was easy for me to modify.&lt;/p&gt;
&lt;h2 id=&quot;what-is-a-bookmarklet&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/obsidian-web-clipper#what-is-a-bookmarklet&quot;&gt;What is a bookmarklet?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A &lt;a href=&quot;https://en.wikipedia.org/wiki/Bookmarklet&quot;&gt;bookmarklet&lt;/a&gt; is a browser bookmark that runs some JavaScript code every time you click it.&lt;/p&gt;
&lt;p&gt;You can create a bookmarklet by creating a new bookmark in your browser,
but instead of providing a link to a website, you give it a &lt;code&gt;javascript&lt;/code&gt; URI.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;javascript: alert(&quot;Go eat ice cream!&quot;)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;So the bookmarklet above would show a “Go eat ice cream!” alert every time you click it (and I highly recommend you install it).&lt;/p&gt;
&lt;h2 id=&quot;my-obsidian-web-clipper&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/obsidian-web-clipper#my-obsidian-web-clipper&quot;&gt;My Obsidian web clipper&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My version of the bookmarklet is based on Stephan Ango’s &lt;a href=&quot;https://gist.github.com/kepano/90c05f162c37cf730abb8ff027987ca3&quot;&gt;Obsidian web clipper&lt;/a&gt;,
so it does pretty much the same thing, but with these differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;npm dependencies are loaded as ECMAScript modules from &lt;a href=&quot;https://www.jsdelivr.com/?docs=esm&quot;&gt;jsDelivr&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Clippings of entire web pages and clippings of selections are stored in &lt;em&gt;separate&lt;/em&gt; Obsidian folders: &lt;code&gt;Clippings&lt;/code&gt; and &lt;code&gt;Clippings/Quotes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Clippings of selections (quotes) of the same web page are &lt;em&gt;appended&lt;/em&gt; to the same Obsidian note.&lt;/li&gt;
&lt;li&gt;Quotes include the selected &lt;a href=&quot;https://web.dev/text-fragments/&quot;&gt;text fragment&lt;/a&gt; in the source link. So visiting the quote’s source link will scroll you to, and highlight, the clipped text on the web page. This works natively in current Chromium and Safari, and in Firefox 131 and later. For older or unsupported browsers, &lt;a href=&quot;https://github.com/GoogleChromeLabs/link-to-text-fragment#installation&quot;&gt;this browser extension&lt;/a&gt; can be installed to polyfill the functionality.&lt;/li&gt;
&lt;li&gt;An alert dialog will show when clipping fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/131&quot;&gt;Firefox 131 added native text-fragment support&lt;/a&gt;,
while older Firefox versions still need a polyfill.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;how-to-use-it&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/obsidian-web-clipper#how-to-use-it&quot;&gt;How to use it?&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Drag this link to your bookmarks: &lt;a href=&quot;javascript:(function(){function _getSelection(e){if(void 0===window.getSelection)return{hasSelection:!1,html:&amp;quot;&amp;quot;,textFragment:&amp;quot;&amp;quot;};const t=window.getSelection();if(!t||t.rangeCount&lt;1)return{hasSelection:!1,html:&amp;quot;&amp;quot;,textFragment:&amp;quot;&amp;quot;};const{status:n,fragment:o}=e(t),i=_makeTextFragmentDirective(n,o),r=window.document.createElement(&amp;quot;div&amp;quot;);for(let e=0,n=t.rangeCount;e&lt;n;++e)r.appendChild(t.getRangeAt(e).cloneContents());const l=r.innerHTML;return{hasSelection:Boolean(l),html:l,textFragment:i}}function _makeTextFragmentDirective(e,t){if(0!==e)return&amp;quot;&amp;quot;;const n=t.prefix?`${encodeURIComponent(t.prefix)}-,`:%22%22,o=t.suffix?`,-${encodeURIComponent(t.suffix)}`:%22%22;return`#:~:text=${n}${encodeURIComponent(t.textStart)}${t.textEnd?%60,${encodeURIComponent(t.textEnd)}%60:%22%22}${o}%60}function%20_makeObsidianNoteContent({author:e,body:t,excerpt:n,selection:o,title:i,url:r}){let%20l=new%20URL(r);l.hash=%22%22,l=l.toString();const%20a=new%20Date;if(o.hasSelection){return%60%3E%20[!quote]%20${a.toLocaleDateString(void%200,{weekday:%22short%22,year:%22numeric%22,month:%22short%22,day:%22numeric%22,hour:%22numeric%22,minute:%22numeric%22})}%20%E2%80%A2%20[Source](${l}${o.textFragment})\n\n${t}\n\n---\n\n%60}{const%20o=n!==i?n:%22%22,[r]=a.toISOString().split(%22T%22);return%60---\ntitle:%20${JSON.stringify(i)}\ndescription:%20${JSON.stringify(o)}\ndate:%20${r}\ntags:\n%20%20-%20clipping\n---\n\n%3E%20[!note]\n%3E%20${%60[${i}](${l})%60}${e?%22%20by%20%22+e:%22%22}\n\n${t}\n%60}}function%20_makeObsidianUri({config:e,content:t,selection:n,title:o}){const%20i={content:t,file:%60${n.hasSelection?e.selectionFolderName:e.folderName}/${o.replace(/:/g,%22%22).replace(/\//g,%22-%22).replace(/\\/g,%22-%22)}%60};n.hasSelection&amp;amp;&amp;amp;(i.append=%22true%22);return%60obsidian://new?${Object.entries(i).map((([e,t])=%3E%60${e}=${encodeURIComponent(t)}%60)).join(%22&amp;amp;%22)}%60}Promise.all([import(%22https://cdn.jsdelivr.net/npm/@mozilla/readability/+esm%22),import(%22https://cdn.jsdelivr.net/npm/turndown/+esm%22),import(%22https://cdn.jsdelivr.net/npm/text-fragments-polyfill/dist/fragment-generation-utils.js/+esm%22),Promise.resolve({folderName:%22Clippings%22,selectionFolderName:%22Clippings/Quotes%22})]).then((([e,t,n,o])=%3E{const{Readability:i}=e.default,{default:r}=t,{generateFragment:l}=n,a=_getSelection(l),{byline:c,content:s,excerpt:d,title:m}=new%20i(window.document.cloneNode(!0)).parse(),u=_makeObsidianUri({config:o,content:_makeObsidianNoteContent({author:c,body:new%20r({headingStyle:%22atx%22,hr:%22---%22,bulletListMarker:%22-%22,codeBlockStyle:%22fenced%22}).turndown(a.html||s),excerpt:d,selection:a,title:m,url:window.document.URL}),selection:a,title:m});window.document.location.href=u})).catch((e=%3E{alert(%22Failed%20to%20clip%20to%20Obsidian\n\n%22+e+%22\n\n(see%20the%20browser%20developer%20console%20for%20more%20details)%22)}));}());&quot;&gt;Clip to Obsidian&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visit a web page:&lt;/p&gt;
&lt;p&gt;a. To clip an entire web page: click the bookmark.&lt;/p&gt;
&lt;p&gt;b. To only clip part of a web page: first select some text (can include images), then click the bookmark.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;known-issues&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/obsidian-web-clipper#known-issues&quot;&gt;Known issues&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Clipping can fail on web pages whose &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP&quot;&gt;Content Security Policy&lt;/a&gt; (CSP) blocks bookmarklets or third-party script loading (e.g. you can’t clip Reddit and Twitter posts).&lt;/li&gt;
&lt;li&gt;Clipping selections does not work in Safari because Safari’s confirmation dialog “unselects” any content before clipping (so it always clips the entire web page).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-code&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/obsidian-web-clipper#the-code&quot;&gt;The code&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Feel free to remix the code below.
After changing the code, you can turn it into a bookmarklet with &lt;a href=&quot;https://make-bookmarklets.com/&quot;&gt;Make Bookmarklets&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;/**
 * Obsidian web clipper (bookmarklet).
 *
 * Based on Stephan Ango&apos;s &quot;Obsidian web clipper&quot;.
 * @see {@link https://stephanango.com/obsidian-web-clipper}
 *
 * Uses jsDelivr to import npm dependencies as ESM modules.
 * @see {@link https://www.jsdelivr.com/?docs=esm}
 *
 * Made into a bookmarklet with &quot;Make Bookmarklets&quot;.
 * @see {@link https://make-bookmarklets.com/}
 */
Promise.all([
  // Dependencies.
  import(&quot;https://cdn.jsdelivr.net/npm/@mozilla/readability/+esm&quot;),
  import(&quot;https://cdn.jsdelivr.net/npm/turndown/+esm&quot;),
  import(
    &quot;https://cdn.jsdelivr.net/npm/text-fragments-polyfill/dist/fragment-generation-utils.js/+esm&quot;
  ),

  // Config.
  Promise.resolve({
    // Clippings of entire web pages will be stored as separate notes in
    // this Obsidian folder.
    folderName: &quot;Clippings&quot;,

    // Clippings of selections will be stored in this Obsidian folder,
    // where clippings of the same web page will be appended to the same
    // Obsidian note.
    selectionFolderName: &quot;Clippings/Quotes&quot;,
  }),
])
  .then(([readabilityJs, turndownJs, textFragmentsPolyfillJs, config]) =&amp;gt; {
    const { Readability } = readabilityJs.default
    const { default: Turndown } = turndownJs
    const { generateFragment } = textFragmentsPolyfillJs
    const selection = _getSelection(generateFragment)

    // Readability removes clutter from web pages.
    // It&apos;s the same library that&apos;s used in Firefox&apos;s Reader View.
    // See: https://www.npmjs.com/package/@mozilla/readability
    const {
      byline: author,
      content,
      excerpt,
      title,
    } = new Readability(window.document.cloneNode(true)).parse()

    // Converts HTML to Markdown.
    // See: https://www.npmjs.com/package/turndown
    const markdown = new Turndown({
      headingStyle: &quot;atx&quot;,
      hr: &quot;---&quot;,
      bulletListMarker: &quot;-&quot;,
      codeBlockStyle: &quot;fenced&quot;,
    }).turndown(selection.html || content)

    const obsidianContent = _makeObsidianNoteContent({
      author,
      body: markdown,
      excerpt,
      selection,
      title,
      url: window.document.URL,
    })

    const obsidianUri = _makeObsidianUri({
      config,
      content: obsidianContent,
      selection,
      title,
    })

    window.document.location.href = obsidianUri
  })
  .catch((error) =&amp;gt; {
    alert(
      &quot;Failed to clip to Obsidian&quot; +
        &quot;\n\n&quot; +
        error +
        &quot;\n\n&quot; +
        &quot;(see the browser developer console for more details)&quot;,
    )
  })

function _getSelection(generateFragmentFn) {
  if (typeof window.getSelection === &quot;undefined&quot;) {
    return {
      hasSelection: false,
      html: &quot;&quot;,
      textFragment: &quot;&quot;,
    }
  }

  const sel = window.getSelection()
  if (!sel || sel.rangeCount &amp;lt; 1) {
    return {
      hasSelection: false,
      html: &quot;&quot;,
      textFragment: &quot;&quot;,
    }
  }

  const { status, fragment } = generateFragmentFn(sel)
  const textFragment = _makeTextFragmentDirective(status, fragment)
  const container = window.document.createElement(&quot;div&quot;)
  for (let i = 0, len = sel.rangeCount; i &amp;lt; len; ++i) {
    container.appendChild(sel.getRangeAt(i).cloneContents())
  }
  const html = container.innerHTML
  return {
    hasSelection: Boolean(html),
    html,
    textFragment,
  }
}

/**
 * Makes the text fragment directive to highlight a text selection.
 *
 * Current Chromium, Safari, and Firefox 131+ browsers support text fragments.
 * @see {@link https://web.dev/text-fragments/}
 *
 * But a browser extension can be installed to polyfill the functionality.
 * @see {@link https://github.com/GoogleChromeLabs/link-to-text-fragment}
 */
function _makeTextFragmentDirective(status, fragment) {
  if (status !== 0) {
    // Non-0 status means error.
    // See: https://github.com/GoogleChromeLabs/link-to-text-fragment/blob/main/fragment-generation-utils.js#L779
    return &quot;&quot;
  }

  const prefix = fragment.prefix ? `${encodeURIComponent(fragment.prefix)}-,` : &quot;&quot;
  const suffix = fragment.suffix ? `,-${encodeURIComponent(fragment.suffix)}` : &quot;&quot;
  const start = encodeURIComponent(fragment.textStart)
  const end = fragment.textEnd ? `,${encodeURIComponent(fragment.textEnd)}` : &quot;&quot;
  return `#:~:text=${prefix}${start}${end}${suffix}`
}

/**
 * Makes the Obsidian note content.
 *
 * For web page clippings only (i.e. not web page selection clippings):
 *
 * Uses YAML front matter to add metadata about the clipping to the note.
 * @see {@link https://help.obsidian.md/Editing+and+formatting/Metadata}
 *
 * Uses a comment to link to a daily note (you can&apos;t link to other notes
 * in the front matter).
 * @see {@link https://help.obsidian.md/Editing+and+formatting/Basic+formatting+syntax#Comments}
 */
function _makeObsidianNoteContent({ author, body, excerpt, selection, title, url }) {
  // Keep query parameters because they can identify the document or content.
  // The hash is replaced for selections by the text fragment below.
  let cleanUrl = new URL(url)
  cleanUrl.hash = &quot;&quot;
  cleanUrl = cleanUrl.toString()

  const now = new Date()

  if (selection.hasSelection) {
    // `locales` is set to `undefined` to use the default locale.
    // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString
    const prettyDate = now.toLocaleDateString(undefined, {
      weekday: &quot;short&quot;,
      year: &quot;numeric&quot;,
      month: &quot;short&quot;,
      day: &quot;numeric&quot;,
      hour: &quot;numeric&quot;,
      minute: &quot;numeric&quot;,
    })
    return `&amp;gt; [!quote] ${prettyDate} &amp;amp;bull; [Source](${cleanUrl}${selection.textFragment})

${body}

---

`
  } else {
    const summary = excerpt !== title ? excerpt : &quot;&quot;
    const [yyyy_mm_dd] = now.toISOString().split(&quot;T&quot;)
    const titleLink = `[${title}](${cleanUrl})`
    return `---
title: ${JSON.stringify(title)}
description: ${JSON.stringify(summary)}
date: ${yyyy_mm_dd}
tags:
  - clipping
---

&amp;gt; [!note]
&amp;gt; ${titleLink}${author ? &quot; by &quot; + author : &quot;&quot;}

${body}
`
  }
}

/**
 * Makes the Obsidian URI to create/append to a note.
 * @see {@link https://help.obsidian.md/Advanced+topics/Using+Obsidian+URI#Action+%60new%60}
 */
function _makeObsidianUri({ config, content, selection, title }) {
  const folderName = selection.hasSelection ? config.selectionFolderName : config.folderName

  // NOTE: characters &quot;:&quot;, &quot;/&quot; and &quot;\&quot; are not allowed in file names.
  const fileName = title.replace(/:/g, &quot;&quot;).replace(/\//g, &quot;-&quot;).replace(/\\/g, &quot;-&quot;)

  const query = {
    content,
    file: `${folderName}/${fileName}`,
  }

  if (selection.hasSelection) {
    // NOTE: &quot;boolean&quot; params trigger with any truthy value, like
    // `append=false`.
    query.append = &quot;true&quot;
  }

  // NOTE: URLSearchParams().toString() encoding leads to unexpected
  // behavior, so use `encodeURIComponent()` instead.
  const queryString = Object.entries(query)
    .map(([k, v]) =&amp;gt; `${k}=${encodeURIComponent(v)}`)
    .join(&quot;&amp;amp;&quot;)

  return `obsidian://new?${queryString}`
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content:encoded></item><item><title>example.com</title><link>https://dans.land/garden/computer-networks/example-com</link><guid isPermaLink="true">https://dans.land/garden/computer-networks/example-com</guid><description>Domains reserved by the Internet Assigned Numbers Authority (IANA).</description><pubDate>Fri, 09 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The domains &lt;a href=&quot;https://www.iana.org/domains/reserved&quot;&gt;reserved by IANA&lt;/a&gt; include a few that are meant for documentation and examples.
One of them is &lt;a href=&quot;https://example.com/&quot;&gt;example.com&lt;/a&gt;.
These domains can’t be registered or transferred.&lt;/p&gt;
&lt;p&gt;These example domains also appear in IANA’s &lt;a href=&quot;https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml&quot;&gt;Special-Use Domain Names registry&lt;/a&gt;.
This is a broader registry of names whose special handling is defined by IETF standards.&lt;/p&gt;
&lt;h2 id=&quot;special-tlds&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/example-com#special-tlds&quot;&gt;Special TLDs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The most notable special-use &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/computer-networks/dns#top-level-domains-and-subdomains&quot; data-garden-link=&quot;&quot;&gt;TLDs&lt;/a&gt; are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.test&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.example&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.invalid&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.localhost&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-to-use-special-domains&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/example-com#how-to-use-special-domains&quot;&gt;How to use special domains?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/rfc2606/&quot;&gt;RFC 2606&lt;/a&gt; specifies best practices on how to use the special domains.
It recommends the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;.test&lt;/strong&gt; domains are recommended for testing DNS-related code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.example&lt;/strong&gt; domains are recommended for documentation and examples.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.invalid&lt;/strong&gt; domains are recommended for demonstrating invalid domain names.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.localhost&lt;/strong&gt; domains are reserved for loopback addresses to the local host (so local networks don’t break).&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;There’s also &lt;a href=&quot;https://datatracker.ietf.org/doc/rfc6761/&quot;&gt;RFC 6761&lt;/a&gt; with more information, like how DNS servers should handle these domains.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;why-are-special-domains-useful&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/example-com#why-are-special-domains-useful&quot;&gt;Why are special domains useful?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Special-use domains prevent tests and documentation from unexpectedly referring to a name that someone registered.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Their exact behavior depends on the name.
For example, &lt;code&gt;.invalid&lt;/code&gt; should return a negative DNS response, &lt;code&gt;.localhost&lt;/code&gt; should resolve to loopback addresses, and &lt;code&gt;.test&lt;/code&gt; may behave differently depending on the network’s configuration.&lt;/p&gt;
&lt;p&gt;Let’s say I make up a domain for (local) testing, where I expect certain behavior (e.g. it must resolve, or it must fail).
It could happen that at some point the domain becomes available, gets registered, and now my test will behave unexpectedly.&lt;/p&gt;
&lt;p&gt;This is what happened with the &lt;code&gt;.dev&lt;/code&gt; TLD!
It was sometimes used for local testing before Google Registry &lt;a href=&quot;https://blog.google/innovation-and-ai/technology/developers-tools/hello-dev/&quot;&gt;became its operator&lt;/a&gt; through ICANN’s new gTLD program and launched it publicly.&lt;/p&gt;</content:encoded></item><item><title>DNS</title><link>https://dans.land/garden/computer-networks/dns</link><guid isPermaLink="true">https://dans.land/garden/computer-networks/dns</guid><description>What I learned about the Domain Name System (DNS) so far.</description><pubDate>Sat, 03 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My understanding of DNS was always pretty basic.
But since I started working more with hosting infrastructure, I’ve learned a lot more about it.&lt;/p&gt;
&lt;p&gt;I think DNS is really cool, but it &lt;em&gt;is&lt;/em&gt; complicated.
There are a lot of moving parts and terminology you need to know about to understand it.
So I decided to write a bit about this.
Mostly to capture and solidify my learnings, but maybe it can also be useful to others.&lt;/p&gt;
&lt;h2 id=&quot;why-do-we-need-dns&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#why-do-we-need-dns&quot;&gt;Why do we need DNS?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The internet is a &lt;em&gt;massive&lt;/em&gt; system of interconnected computer networks,
and devices connected to this network communicate with each other by sending “packets of data”.
But to make sure that these packets are routed to the correct destination, a &lt;strong&gt;protocol&lt;/strong&gt; must be followed.&lt;/p&gt;
&lt;p&gt;What’s a protocol? It’s basically a set of rules that need to be followed to achieve “something”.&lt;/p&gt;
&lt;p&gt;For example, to mail a letter, the protocol is that you must:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use an envelope.&lt;/li&gt;
&lt;li&gt;Write the sender and delivery address on the envelope (using a name, street address, city and zip code).&lt;/li&gt;
&lt;li&gt;Use a postage stamp.&lt;/li&gt;
&lt;li&gt;Deposit the envelope in a mailbox or at a post office.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But if one of these rules is broken (e.g. because phone numbers were used for rule 2 above) the letter will not be delivered.&lt;/p&gt;
&lt;p&gt;It’s a bit like that on the internet.
But the protocol that’s used is called the &lt;strong&gt;Internet Protocol&lt;/strong&gt; (IP),
and instead of using mail addresses to deliver mail to the correct destination,
&lt;strong&gt;IP addresses&lt;/strong&gt; must be used to deliver packets of data to the correct destination&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;IP addresses identify network interfaces or destinations, but they are not universally unique device identifiers:
private addresses are reused, and anycast addresses can represent multiple interfaces.
For example, if a device wants to visit this website it must (at the time of this writing) go to IP address &lt;code&gt;188.114.96.0&lt;/code&gt; or &lt;code&gt;188.114.97.0&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;IP addresses work great for machines and robots because they &lt;em&gt;love&lt;/em&gt; numbers.
But us humans usually have difficulty remembering them and we prefer using a more memorable &lt;strong&gt;domain name&lt;/strong&gt; instead.&lt;/p&gt;
&lt;p&gt;But on the internet IP addresses must be used. So how can you type a domain name in a web browser and somehow still end up at the correct IP address?
This is the main problem that DNS solves: &lt;strong&gt;DNS can look up the IP address of a domain name&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-dns&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#what-is-dns&quot;&gt;What is DNS?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Practically speaking, DNS is like a phone book&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; for the internet.&lt;/p&gt;
&lt;p&gt;Technically speaking, DNS is a distributed naming system that consists of many servers spread across the globe.&lt;/p&gt;
&lt;p&gt;I like to think about DNS as a very large partitioned database that organizes, stores, and retrieves information about domain names.
To do all of this, DNS has the following main components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The domain name space (to organize domain names).&lt;/li&gt;
&lt;li&gt;Name servers and resource records (to store information about domain names).&lt;/li&gt;
&lt;li&gt;Resolvers (to retrieve information about domain names).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-domain-name-space&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#the-domain-name-space&quot;&gt;The domain name space&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The domain name space is a conceptual model that organizes all domain names on the internet,
and it can be visualized as a hierarchical structure that looks like a &lt;a href=&quot;https://en.wikipedia.org/wiki/Tree_(data_structure)&quot;&gt;tree&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This hierarchy is reflected in domain names themselves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each part of a domain name that is separated by a &lt;code&gt;.&lt;/code&gt; (dot) is called a &lt;strong&gt;label&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Each label represents a node in the tree, and is a “sublevel” in the naming hierarchy.&lt;/li&gt;
&lt;li&gt;The root of the tree is the “nameless” label &lt;code&gt;.&lt;/code&gt; (dot), also called the &lt;strong&gt;root domain&lt;/strong&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-4&quot; id=&quot;user-content-fnref-4&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, the labels of the domain names:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;www.framer.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;github.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dans.land&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;en.wikipedia.org&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Can be visualized in the domain name space like this:&lt;/p&gt;
&lt;div class=&quot;mermaid-frame&quot; data-mermaid-width=&quot;603&quot; data-mermaid-height=&quot;382&quot; style=&quot;--mermaid-width: 603px; --mermaid-ratio: 603 / 382&quot;&gt;&lt;pre class=&quot;mermaid&quot;&gt;graph TD;
  accTitle: Domain name hierarchy
  accDescr: A hierarchy from the DNS root to top-level domains, second-level domains, and subdomains.
  A[.]--&amp;gt;B[com];
  A--&amp;gt;C[land];
  A--&amp;gt;D[org];
  B--&amp;gt;E[framer];
  B--&amp;gt;F[github];
  C--&amp;gt;G[dans]
  D--&amp;gt;H[wikipedia]
  E--&amp;gt;I[www]
  H--&amp;gt;J[en]&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;top-level-domains-and-subdomains&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#top-level-domains-and-subdomains&quot;&gt;Top-level domains and subdomains&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;By following the tree of the domain name space from top-to-bottom, the labels of a domain name go from most generic (&lt;code&gt;.&lt;/code&gt;) to most specific (e.g. &lt;code&gt;www&lt;/code&gt;).
And depending on what “level” these labels sit in the tree, they are referred to differently.&lt;/p&gt;
&lt;p&gt;When reading a domain name from left-to-right:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The right-most label is called the &lt;strong&gt;top-level domain&lt;/strong&gt; (TLD). There are different kinds of TLDs&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-5&quot; id=&quot;user-content-fnref-5&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;, but the most notable are:
&lt;ul&gt;
&lt;li&gt;Generic top-level domains (gTLDs), like &lt;code&gt;com&lt;/code&gt; or &lt;code&gt;org&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Country code top-level domains (ccTLDs), like &lt;code&gt;uk&lt;/code&gt; or &lt;code&gt;nl&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The label before the TLD is called the &lt;strong&gt;second-level domain&lt;/strong&gt; (2LD). The label before that is called the &lt;strong&gt;third-level domain&lt;/strong&gt; (3LD). This can go on and on: fourth-level, fifth-level, etc. But often all labels before the 2LD are just called a &lt;strong&gt;subdomain&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, for the domain name &lt;code&gt;www.bbc.co.uk&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;uk&lt;/code&gt; is the TLD (ccTLD).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;co&lt;/code&gt; is the 2LD.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bbc&lt;/code&gt; is the 3LD.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;www&lt;/code&gt; is the 4LD.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;name-servers-and-resource-records&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#name-servers-and-resource-records&quot;&gt;Name servers and resource records&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Each label in the domain name space will usually have some information associated with it (e.g. an IP address).
This information is represented by &lt;strong&gt;resource records&lt;/strong&gt; (usually called DNS records).
A zone file is one text-file representation of those records, and DNS servers that serve resource records are called &lt;strong&gt;name servers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;There are different kinds of resource records, and I won’t cover all of them on this page, but 3 important ones are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NS records&lt;/strong&gt; store the name server of a domain name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A records&lt;/strong&gt; store the IPv4 address of a domain name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CNAME records&lt;/strong&gt; point to another domain name.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;dns-zones&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#dns-zones&quot;&gt;DNS zones&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Each &lt;strong&gt;DNS zone&lt;/strong&gt; is an administratively managed portion of the domain name space and has an &lt;strong&gt;operator&lt;/strong&gt;:
an organization responsible for managing that portion.
Name servers serve the zone’s records.&lt;/p&gt;
&lt;p&gt;DNS zones usually don’t map to domain names or DNS servers exactly, so they can be a bit ambiguous.
But they will usually map to level(s) of the domain name space tree (like the root zone, but more on that later).
This means that &lt;strong&gt;zones only serve parts of the information in the domain name space&lt;/strong&gt;, where a zone can be served by multiple name servers.&lt;/p&gt;
&lt;p&gt;I like to think about DNS zones as partitions of the entire database.
DNS needs to store a lot of information (and make it globally available), so it splits up its database into zones.
To make sure the system as a whole scales and runs reliably, each zone has an operator that’s responsible for it.&lt;/p&gt;
&lt;h3 id=&quot;resolvers&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#resolvers&quot;&gt;Resolvers&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Name servers only store part of the domain name space, so how can DNS retrieve information for every name in the domain name space?
Most name servers just point to &lt;em&gt;other&lt;/em&gt; name servers,
and it’s up to a &lt;strong&gt;recursive resolver&lt;/strong&gt; (also called a recursor) to follow these “pointers” and retrieve resource records.
A browser or operating system usually has a &lt;strong&gt;stub resolver&lt;/strong&gt;, which forwards its query to a recursive resolver instead of following the chain itself.&lt;/p&gt;
&lt;h2 id=&quot;how-does-dns-work&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#how-does-dns-work&quot;&gt;How does DNS work?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So far we’ve covered the main components of DNS, but to understand how it works we first need to explicitly identify the different kinds of DNS servers and how they interact with each other.&lt;/p&gt;
&lt;p&gt;There are 4 different kinds of servers needed to make DNS work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Root name servers&lt;/strong&gt; are the name servers that serve the &lt;strong&gt;DNS root zone&lt;/strong&gt;. This is a special DNS zone that contains &lt;em&gt;all&lt;/em&gt; TLDs of the domain name space. The DNS root zone consists of 13 root name servers&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-6&quot; id=&quot;user-content-fnref-6&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;, and each root name server contains the &lt;a href=&quot;https://www.iana.org/domains/root/db&quot;&gt;root zone database&lt;/a&gt;. This contains NS records that delegate TLDs to their name servers, and may include glue records with their IP addresses. Such lists are published as plain text files called &lt;strong&gt;DNS zone files&lt;/strong&gt; (like the &lt;a href=&quot;https://www.internic.net/domain/root.zone&quot;&gt;root zone file&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLD name servers&lt;/strong&gt; serve a TLD zone that contains delegations for 2LDs (for a specific TLD). These delegations return NS records naming the authoritative name servers, and may include glue records with their IP addresses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authoritative name servers&lt;/strong&gt; are authoritative for a zone: they serve the resource records for that administratively managed portion of the domain name space.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recursive resolvers&lt;/strong&gt; receive requests from stub resolvers to find resource records (e.g. an IP address). They send &lt;strong&gt;queries&lt;/strong&gt; to name servers and receive resource record(s) back as an &lt;strong&gt;answer&lt;/strong&gt;. A recursive resolver that queries the DNS hierarchy itself uses a &lt;a href=&quot;https://www.internic.net/domain/named.root&quot;&gt;hard-coded list&lt;/a&gt; of the 13 root name servers as its starting point. It can also use cached data or forward the query to another recursive resolver. Clients often use the recursive resolver provided by their network or Internet Service Provider (ISP)&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-7&quot; id=&quot;user-content-fnref-7&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;About authoritative name servers&lt;/p&gt;
&lt;p&gt;I used to be confused about what authoritative name servers are and how they differ from other name servers.&lt;/p&gt;
&lt;p&gt;But an authoritative name server is just a name server that serves authoritative data for a zone.
So which name server is authoritative depends on the queried name and its zone.&lt;/p&gt;
&lt;p&gt;For example, root name servers are authoritative for the root zone,
TLD name servers are authoritative for a TLD zone,
and when querying the A record for a domain name,
the name server serving the zone that contains the record is authoritative.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With that covered, we can finally answer the question below.&lt;/p&gt;
&lt;h3 id=&quot;what-happens-when-you-visit-a-website-in-your-browser&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#what-happens-when-you-visit-a-website-in-your-browser&quot;&gt;What happens when you visit a website in your browser?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The following simplified example assumes the recursive resolver has nothing useful cached and queries the DNS hierarchy itself:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The browser’s stub resolver sends a request to a recursive resolver to find the A record of the entered domain name.&lt;/li&gt;
&lt;li&gt;The resolver sends a query to one of the 13 root name servers to find the TLD name server of the domain name. When found, the root name server sends an NS record back (with the name of the TLD name server) as the answer to the resolver.&lt;/li&gt;
&lt;li&gt;The resolver sends a query to the TLD name server to find the authoritative name server of the 2LD of the domain name. When found, the TLD name server sends an NS record back (with the name of the authoritative name server) as the answer to the resolver.&lt;/li&gt;
&lt;li&gt;The resolver sends a query to the authoritative name server to find the A record of the domain name. When found, the authoritative name server sends an A record back (with the IP address of the domain name) as the answer to the resolver.&lt;/li&gt;
&lt;li&gt;The resolver responds with the IP address of the domain name to the browser.&lt;/li&gt;
&lt;li&gt;The browser can now make an HTTP request to the IP address and fetch the website.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;The steps above are for uncached queries&lt;/p&gt;
&lt;p&gt;Since there can be a lot of steps needed to look up information for a domain name, resolvers will cache the results of queries.&lt;/p&gt;
&lt;p&gt;For example, when a query is made for a domain name that was recently looked up,
the resolver can skip (some of) the steps above and return the cached result(s) immediately.&lt;/p&gt;
&lt;p&gt;Caching can happen at several points: on recursive resolvers, operating systems, and browsers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;bonus-how-is-the-domain-name-system-managed&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#bonus-how-is-the-domain-name-system-managed&quot;&gt;Bonus: how is the domain name system managed?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We now know that DNS is basically a very large database that’s split up into zones, and that zones are managed by operators.
But how do operators work together? How do operators know about changes that occur in the domain name space (like when a new domain name is registered)?
And who oversees all of this?&lt;/p&gt;
&lt;h3 id=&quot;icann-and-iana&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#icann-and-iana&quot;&gt;ICANN and IANA&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.icann.org/&quot;&gt;ICANN&lt;/a&gt; (Internet Corporation for Assigned Names and Numbers) and &lt;a href=&quot;https://www.iana.org/&quot;&gt;IANA&lt;/a&gt; (Internet Assigned Numbers Authority) are 2 organizations that help provide stability and consistency on the internet.&lt;/p&gt;
&lt;p&gt;ICANN helps with administration, oversight, and maintenance, but delegates some of this to IANA (which is part of ICANN).&lt;/p&gt;
&lt;p&gt;For example, ICANN coordinates DNS policy and adding &lt;a href=&quot;https://newgtlds.icann.org/en/about/program&quot;&gt;new TLDs&lt;/a&gt;, and operates 1 of the 13 DNS root name servers.
IANA maintains shared lists of the numbers and names used by internet protocols (such as port numbers and DNS record types), coordinates global IP-address allocations through the regional Internet registries, and manages the DNS root zone.&lt;/p&gt;
&lt;h3 id=&quot;domain-name-registries-and-registrars&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#domain-name-registries-and-registrars&quot;&gt;Domain name registries and registrars&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Besides the root zone database managed by IANA, there are also organizations that manage a database of all 2LDs with a specific TLD.
These organizations are called &lt;strong&gt;registry operators&lt;/strong&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-8&quot; id=&quot;user-content-fnref-8&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;.
Strictly speaking, the databases they maintain are called &lt;strong&gt;registries&lt;/strong&gt;, but often the operator itself will also be referred to as the registry.&lt;/p&gt;
&lt;p&gt;This means each TLD has a registry.
For example, &lt;a href=&quot;https://www.verisign.com/&quot;&gt;Verisign&lt;/a&gt; is the registry for &lt;code&gt;.com&lt;/code&gt; domain names and &lt;a href=&quot;https://www.registry.google/&quot;&gt;Google Registry&lt;/a&gt; is the registry for &lt;code&gt;.dev&lt;/code&gt; domain names.
Verisign and Google actually manage multiple TLDs, but there are also registries that manage a single TLD.&lt;/p&gt;
&lt;p&gt;So how do these registries know about (new) domain names?
Some registries allow you to directly register a domain name with them.
But most registries will partner with a different organization called a &lt;strong&gt;domain name registrar&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Domain name registrars are companies that allow you to register domain names by paying them a fee.
When you register a domain name, you don’t actually buy the domain name.
But you will hold the “right” to use it for a specific amount of time.
You then become the &lt;strong&gt;registrant&lt;/strong&gt; of the domain name and will be considered the “owner” of it.&lt;/p&gt;
&lt;p&gt;Registries allow registrars to partner with them by entering a &lt;strong&gt;Registry-Registrar Agreement&lt;/strong&gt;.
But in order to do so, the registrar must meet the requirements&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-9&quot; id=&quot;user-content-fnref-9&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;9&lt;/a&gt;&lt;/sup&gt; set by the registry (and ICANN).
After the agreement is in place, the registrar may offer their customers to register domain names for the specific TLD(s).
Every time a domain name is registered, renewed, transferred, or expires,
the registrar will then notify&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-10&quot; id=&quot;user-content-fnref-10&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;10&lt;/a&gt;&lt;/sup&gt; the registry,
where for some operations registrars also pay registries (and ICANN) a fee&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fn-11&quot; id=&quot;user-content-fnref-11&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;11&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/rfc1034/&quot;&gt;RFC 1034: Domain names concepts and facilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/rfc9499/&quot;&gt;RFC 9499: DNS Terminology&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cloudflare.com/learning/dns/what-is-dns/&quot;&gt;What is DNS?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.icann.org/resources/pages/what-2012-02-25-en/&quot;&gt;What does ICANN do?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 id=&quot;footnote-label&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/computer-networks/dns#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;The IP protocol is basically the addressing system of the internet, but there’s more needed to deliver packets from source to destination. The exact details are out of scope for this page, but there’s also a transport protocol needed to define rules for how data is sent and received. Ultimately there are multiple protocols needed which are “layered” on top of each other, like &lt;a href=&quot;https://en.wikipedia.org/wiki/Internet_protocol_suite&quot;&gt;TCP/IP&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;This is an IPv4 address, and IP version 4 has been around since 1983. It works great, but we’re running out of unique IPv4 addresses because nowadays even toasters must connect to the internet. This is where IPv6 comes in: IPv6 expands an address from 32 bits to 128 bits to make sure all toasters are covered! For example, &lt;code&gt;2a06:98c1:3120::&lt;/code&gt; is an IPv6 address. But IPv6 is not completely adopted yet, so it’s still common to use IPv4. &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;In case you don’t know, a phone book is literally a &lt;a href=&quot;https://en.wikipedia.org/wiki/Telephone_directory&quot;&gt;book of phone numbers&lt;/a&gt;. A long time ago they were used to find a phone number for a person or business when you only knew their name. &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-4&quot;&gt;
&lt;p&gt;The root domain is typically not specified. For example, you’d usually type &lt;code&gt;github.com&lt;/code&gt; in your browser instead of &lt;code&gt;github.com.&lt;/code&gt; (note the trailing dot). But you can absolutely include it! Strictly speaking, a &lt;strong&gt;Fully Qualified Domain Name&lt;/strong&gt; (FQDN) includes the root and is written with that trailing dot. In everyday use though, &lt;code&gt;github.com&lt;/code&gt; is often still called an FQDN without it. &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-4&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-5&quot;&gt;
&lt;p&gt;There are &lt;a href=&quot;https://en.wikipedia.org/wiki/Top-level_domain&quot;&gt;6 types of TLDs&lt;/a&gt;: country code (ccTLD), generic (gTLD), generic restricted (grTLD), infrastructure (ARPA), sponsored (sTLD), and test (tTLD) top-level domains. &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-5&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 5&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-6&quot;&gt;
&lt;p&gt;There are 13 clusters of hundreds of physical DNS root servers, distributed all over the globe. You can see them (and their location) on &lt;a href=&quot;https://root-servers.org/&quot;&gt;root-servers.org&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-6&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 6&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-7&quot;&gt;
&lt;p&gt;But you can change this in the network settings of your operating system and use a different resolver, like Cloudflare’s &lt;a href=&quot;https://developers.cloudflare.com/1.1.1.1/&quot;&gt;1.1.1.1&lt;/a&gt; or Google’s &lt;a href=&quot;https://developers.google.com/speed/public-dns/docs/using&quot;&gt;8.8.8.8&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-7&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 7&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-8&quot;&gt;
&lt;p&gt;Registry operators (or registries) are sometimes also called a Network Information Center (NIC). &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-8&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 8&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-9&quot;&gt;
&lt;p&gt;These requirements can differ per registry (and some make them &lt;a href=&quot;https://www.verisign.com/resources/become-a-registrar/&quot;&gt;available online&lt;/a&gt;). For example, most registries require the registrar to be &lt;a href=&quot;https://www.icann.org/en/accredited-registrars&quot;&gt;accredited by ICANN&lt;/a&gt;. Sometimes registries even set rules that affect which &lt;em&gt;registrants&lt;/em&gt; may register a domain name for their TLD (e.g. &lt;a href=&quot;https://get.gov/registration/requirements/&quot;&gt;only US governments&lt;/a&gt; may register a &lt;code&gt;.gov&lt;/code&gt; domain name). &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-9&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 9&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-10&quot;&gt;
&lt;p&gt;Registrars usually use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Extensible_Provisioning_Protocol&quot;&gt;Extensible Provisioning Protocol&lt;/a&gt; (EPP) to interact with registries. &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-10&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 10&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-11&quot;&gt;
&lt;p&gt;The registrar must pay fees every time a domain name is registered, renewed or transferred. There’s the registry fee (as defined in the Registry-Registrar Agreement). There may be an ICANN transaction fee (whose amount can change by fiscal year). But there might also be other fees, like an annual accreditation fee when the registrar is ICANN accredited. &lt;a href=&quot;https://dans.land/garden/computer-networks/dns#user-content-fnref-11&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 11&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>Mastodon alias</title><link>https://dans.land/garden/mastodon-alias</link><guid isPermaLink="true">https://dans.land/garden/mastodon-alias</guid><description>Using a custom domain to alias your Mastodon handle.</description><pubDate>Tue, 03 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;I have since deleted my Mastodon account,
so the endpoints and account URLs mentioned in this post are no longer live.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’m not very active on social media, but I recently created a &lt;a href=&quot;https://joinmastodon.org/&quot;&gt;Mastodon&lt;/a&gt; account.&lt;/p&gt;
&lt;p&gt;I’m still learning about the fediverse.
So I was reading the docs a bit, and that’s when I stumbled upon &lt;a href=&quot;https://docs.joinmastodon.org/spec/webfinger/&quot;&gt;WebFinger&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had never heard of it before, but Mastodon uses WebFinger to figure out the location of an account.
So it can, for example, resolve the account &lt;code&gt;danillouz@mastodon.social&lt;/code&gt; to the location &lt;code&gt;https://mastodon.social/@danillouz&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This location information is returned by a WebFinger endpoint,
which made me wonder.
Could my site, hosted on a custom domain, return this information as well, so that I could use my custom domain as an “alias” for my Mastodon handle?
Turns out you can!
But there are some caveats.&lt;/p&gt;
&lt;h2 id=&quot;whats-webfinger&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#whats-webfinger&quot;&gt;What’s WebFinger?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;WebFinger is a protocol&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/mastodon-alias#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; that allows information about people or entities to be discovered over HTTP.
It basically resolves some sort of URI identifier (like an email address, Mastodon account, or phone number) to a location (i.e. a URL),
which can be retrieved by making a WebFinger request.&lt;/p&gt;
&lt;h3 id=&quot;making-a-webfinger-request&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#making-a-webfinger-request&quot;&gt;Making a WebFinger request&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A WebFinger request is an HTTP &lt;code&gt;GET&lt;/code&gt; request to a resource.
The resource is a well-known URI with a query target.
The query target identifies the entity to get the location for, which is specified via the &lt;code&gt;?resource=&lt;/code&gt; query parameter in the request.
The endpoint then returns the location information as JSON.&lt;/p&gt;
&lt;p&gt;For example, to get WebFinger information for the Mastodon account &lt;code&gt;danillouz@mastodon.social&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/mastodon-alias#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, you need to make the following request:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP request&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;GET /.well-known/webfinger?resource=acct:danillouz@mastodon.social
HOST: mastodon.social&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP response&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;200 OK
Content-Type: application/json

{
  &quot;subject&quot;: &quot;acct:danillouz@mastodon.social&quot;,
  &quot;aliases&quot;: [
    &quot;https://mastodon.social/@danillouz&quot;,
    &quot;https://mastodon.social/users/danillouz&quot;
  ],
  &quot;links&quot;: [
    {
      &quot;rel&quot;: &quot;http://webfinger.net/rel/profile-page&quot;,
      &quot;type&quot;: &quot;text/html&quot;,
      &quot;href&quot;: &quot;https://mastodon.social/@danillouz&quot;
    },
    {
      &quot;rel&quot;: &quot;self&quot;,
      &quot;type&quot;: &quot;application/activity+json&quot;,
      &quot;href&quot;: &quot;https://mastodon.social/users/danillouz&quot;
    },
    {
      &quot;rel&quot;: &quot;http://ostatus.org/schema/1.0/subscribe&quot;,
      &quot;template&quot;: &quot;https://mastodon.social/authorize_interaction?uri={uri}&quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;You can replace the Mastodon domain and username with your own, to get your information instead:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;https://{MASTODON_DOMAIN}/.well-known/webfinger?resource=acct:{MASTODON_USERNAME}@{MASTODON_DOMAIN}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;why-is-webfinger-used&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#why-is-webfinger-used&quot;&gt;Why is WebFinger used?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;On Mastodon, users have accounts on different servers,
like &lt;a href=&quot;https://mastodon.social/&quot;&gt;mastodon.social&lt;/a&gt; or &lt;a href=&quot;https://mas.to/&quot;&gt;mas.to&lt;/a&gt;.
So even though the handles &lt;code&gt;danillouz@mastodon.social&lt;/code&gt; and &lt;code&gt;danillouz@mas.to&lt;/code&gt; share the same “local” username &lt;code&gt;danillouz&lt;/code&gt;, they are different accounts.&lt;/p&gt;
&lt;p&gt;From what I understand, Mastodon’s internal implementation can’t just use the account handle.
It requires the location (provided by WebFinger) to convert an account to a user on its server for things like mentions and search to work.&lt;/p&gt;
&lt;h2 id=&quot;adding-a-webfinger-endpoint&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#adding-a-webfinger-endpoint&quot;&gt;Adding a WebFinger endpoint&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The RFC mentions that WebFinger information is static:&lt;/p&gt;
&lt;blockquote cite=&quot;https://www.rfc-editor.org/rfc/rfc7033#section-1&quot;&gt;
&lt;p&gt;The information is intended to be static in nature,
and, as such, WebFinger is not intended to be used to return dynamic information like the temperature of a CPU or the current toner level in a laser printer.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7033#section-1&quot;&gt;https://www.rfc-editor.org/rfc/rfc7033#section-1&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;p&gt;So if you can host some static JSON on your custom domain, you can add a WebFinger endpoint.&lt;/p&gt;
&lt;p&gt;You can do this by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/mastodon-alias#making-a-webfinger-request&quot;&gt;Making a WebFinger request&lt;/a&gt; for your Mastodon account to get your WebFinger information.&lt;/li&gt;
&lt;li&gt;Copy and paste the WebFinger JSON response from step 1 into a static file.&lt;/li&gt;
&lt;li&gt;Returning the JSON&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/mastodon-alias#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; from step 2 whenever an HTTP &lt;code&gt;GET&lt;/code&gt; request is made to &lt;code&gt;/.well-known/webfinger?resource=acct:{MASTODON_USERNAME}@{MASTODON_DOMAIN}&lt;/code&gt; on your custom domain.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With that in place, your custom domain can be used to find your Mastodon account.&lt;/p&gt;
&lt;h3 id=&quot;static-file-endpoint&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#static-file-endpoint&quot;&gt;Static file endpoint&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I’m using &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;, so I just added a &lt;a href=&quot;https://docs.astro.build/en/core-concepts/endpoints/#static-file-endpoints&quot;&gt;static file endpoint&lt;/a&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;src/pages/.well-known/webfinger.json.ts&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import type { APIRoute } from &quot;astro&quot;

const MASTODON_USERNAME = &quot;danillouz&quot;
const MASTODON_DOMAIN = &quot;mastodon.social&quot;

export const GET: APIRoute = async function ({ params, request }) {
  return new Response(
    JSON.stringify({
      subject: `acct:${MASTODON_USERNAME}@${MASTODON_DOMAIN}`,
      aliases: [
        `https://${MASTODON_DOMAIN}/@${MASTODON_USERNAME}`,
        `https://${MASTODON_DOMAIN}/users/${MASTODON_USERNAME}`,
      ],
      links: [
        {
          rel: &quot;http://webfinger.net/rel/profile-page&quot;,
          type: &quot;text/html&quot;,
          href: `https://${MASTODON_DOMAIN}/@${MASTODON_USERNAME}`,
        },
        {
          rel: &quot;self&quot;,
          type: &quot;application/activity+json&quot;,
          href: `https://${MASTODON_DOMAIN}/users/${MASTODON_USERNAME}`,
        },
        {
          rel: &quot;http://ostatus.org/schema/1.0/subscribe&quot;,
          template: `https://${MASTODON_DOMAIN}/authorize_interaction?uri={uri}`,
        },
      ],
    }),
    { headers: { &quot;Content-Type&quot;: &quot;application/jrd+json&quot; } },
  )
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;redirecting-webfinger-requests&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#redirecting-webfinger-requests&quot;&gt;Redirecting WebFinger requests&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Note that the &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/mastodon-alias#static-file-endpoint&quot;&gt;Static file endpoint&lt;/a&gt; I added will only serve the WebFinger information when making the request:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP request&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;GET /.well-known/webfinger.json
Host: www.danillouz.dev&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;But Mastodon will actually make the following request:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP request&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;GET /.well-known/webfinger?resource=acct:danillouz@danillouz.dev
Host: www.danillouz.dev&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Since I have just one Mastodon account,
I chose to just ignore the &lt;code&gt;?resource=&lt;/code&gt; query parameter,
and redirect all requests from &lt;code&gt;/.well-known/webfinger&lt;/code&gt; to &lt;code&gt;/.well-known/webfinger.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I’m using &lt;a href=&quot;https://vercel.com/&quot;&gt;Vercel&lt;/a&gt;, which supports &lt;a href=&quot;https://vercel.com/docs/project-configuration#project-configuration/redirects&quot;&gt;redirects&lt;/a&gt;.
So I can achieve the desired redirect by adding the following rule:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;vercel.json&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;redirects&quot;: [
    {
      &quot;source&quot;: &quot;/.well-known/webfinger&quot;,
      &quot;destination&quot;: &quot;/.well-known/webfinger.json&quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;using-my-custom-domain-as-an-alias&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#using-my-custom-domain-as-an-alias&quot;&gt;Using my custom domain as an alias&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that my custom domain has a WebFinger endpoint, I can find my Mastodon account by using my custom domain!&lt;/p&gt;
&lt;p&gt;For example, searching for &lt;code&gt;hi@danillouz.dev&lt;/code&gt; will now give me a hit.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Mastodon search result for hi@danillouz.dev&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1200&quot; height=&quot;1060&quot; src=&quot;https://dans.land/assets/search.ConjzqVd_Z33GGF.webp&quot; srcset=&quot;https://dans.land/assets/search.ConjzqVd_ZSTPr2.webp 320w, https://dans.land/assets/search.ConjzqVd_vOeWC.webp 480w, https://dans.land/assets/search.ConjzqVd_Z1dgGNx.webp 672w, https://dans.land/assets/search.ConjzqVd_Z1saeGp.webp 960w, https://dans.land/assets/search.ConjzqVd_Z33GGF.webp 1200w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Mastodon search result for hi@danillouz.dev&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;so-how-useful-is-this&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#so-how-useful-is-this&quot;&gt;So how useful is this?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’m not sure to be honest.&lt;/p&gt;
&lt;p&gt;As mentioned before, Mastodon is a bit different: an account handle consists of two parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The local username. For example &lt;code&gt;danillouz&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The server domain. For example &lt;code&gt;mastodon.social&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The docs mention that you should include the server domain when sharing your handle with other people, because otherwise they won’t be able to find you easily:&lt;/p&gt;
&lt;blockquote cite=&quot;https://docs.joinmastodon.org/user/signup/#your-username-and-your-domain&quot;&gt;
&lt;p&gt;Mastodon allows you to skip the second part when addressing people on the same server as you,
but you have to keep in mind when sharing your username with other people,
you need to include the domain or they won’t be able to find you as easily.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://docs.joinmastodon.org/user/signup/#your-username-and-your-domain&quot;&gt;https://docs.joinmastodon.org/user/signup/#your-username-and-your-domain&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;p&gt;So in theory, setting up an alias allows you to create a handle that does not change when migrating to a different Mastodon server.
It might make your account easier to find if people know your custom domain.&lt;/p&gt;
&lt;p&gt;It’s also pretty cool that with the alias you can have a Mastodon handle that includes your custom domain without needing to host your own Mastodon server.
But practically speaking, searching for just the local username on different servers also works as far as I can tell.&lt;/p&gt;
&lt;p&gt;When the docs mentioned that you should include the server domain when sharing your handle,
I thought this meant that someone would always have to search for &lt;code&gt;danillouz@mastodon.social&lt;/code&gt; on servers &lt;em&gt;other&lt;/em&gt; than &lt;code&gt;mastodon.social&lt;/code&gt; to find me.
But this doesn’t appear to be the case.
For example, I can search for &lt;code&gt;danillouz&lt;/code&gt; on &lt;code&gt;mast.to&lt;/code&gt; and it will find me.&lt;/p&gt;
&lt;p&gt;So maybe aliasing your handle isn’t really a good idea?&lt;/p&gt;
&lt;p&gt;I’m not sure if having an “extra” WebFinger endpoint can actually break stuff (can information become stale?).
But there are some caveats when using your custom domain as an alias to be aware of.&lt;/p&gt;
&lt;h2 id=&quot;caveats&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#caveats&quot;&gt;Caveats&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There might be more, but these are the ones I encountered.&lt;/p&gt;
&lt;h3 id=&quot;users-need-to-be-signed-in-to-find-you-via-the-alias&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#users-need-to-be-signed-in-to-find-you-via-the-alias&quot;&gt;Users need to be signed in to find you via the alias&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I created my account on &lt;code&gt;mastodon.social&lt;/code&gt;, and there I can find my account when searching for the alias without problems.
But when I tried finding my account using the alias on a &lt;em&gt;different&lt;/em&gt; server, I was surprised there was no result.&lt;/p&gt;
&lt;p&gt;Turns out that when you’re not signed in to a server, the search API will not use WebFinger to resolve the handle!&lt;/p&gt;
&lt;p&gt;This is what the search request looks like when I’m signed in:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP request&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;GET /api/v2/search?q=hi@danillouz.dev&amp;amp;resolve=true
Host: mastodon.social&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;And this is what the same search request looks like when I’m signed out:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP request&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;GET /api/v2/search?q=hi@danillouz.dev&amp;amp;resolve=false
Host: mastodon.social&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The difference is that the query parameter &lt;code&gt;resolve&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt; when signed in.
But it is set to &lt;code&gt;false&lt;/code&gt; when signed out.&lt;/p&gt;
&lt;p&gt;Checking the v2 search API docs, we can see that &lt;code&gt;resolve&lt;/code&gt; controls if a WebFinger lookup should happen:&lt;/p&gt;
&lt;blockquote cite=&quot;https://docs.joinmastodon.org/methods/search/#query-parameters&quot;&gt;
&lt;p&gt;Boolean. Attempt WebFinger lookup? Defaults to false.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://docs.joinmastodon.org/methods/search/#query-parameters&quot;&gt;https://docs.joinmastodon.org/methods/search/#query-parameters&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;h3 id=&quot;the-alias-behaves-like-a-catch-all&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#the-alias-behaves-like-a-catch-all&quot;&gt;The alias behaves like a “catch-all”&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Since I’m &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/mastodon-alias#redirecting-webfinger-requests&quot;&gt;Redirecting WebFinger requests&lt;/a&gt;, I’m returning the same response for all &lt;code&gt;acct:&lt;/code&gt; queries.
In my original testing, any&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/mastodon-alias#user-content-fn-4&quot; id=&quot;user-content-fnref-4&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; local username could be provided together with my custom domain.&lt;/p&gt;
&lt;p&gt;Mastodon &lt;a href=&quot;https://docs.joinmastodon.org/spec/webfinger/#mastodons-requirements-for-webfinger&quot;&gt;validates&lt;/a&gt; an alias by resolving it to an ActivityPub actor and checking the actor’s canonical WebFinger address.
Both addresses must resolve to the same actor.
So a catch-all can make many usernames resolve to one account, but it does not create separate accounts for them.&lt;/p&gt;
&lt;p&gt;For example, these all worked:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hey@danillouz.dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;737@danillouz.dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lol@danillouz.dev&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#resources&quot;&gt;Resources&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.maartenballiauw.be/posts/2022-11-05-mastodon-own-domain-without-hosting-server&quot;&gt;Mastodon on your own domain without hosting a server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.lindsaykwardell.com/blog/integrate-mastodon-with-astro&quot;&gt;Integrating Mastodon with Astro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 id=&quot;footnote-label&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/mastodon-alias#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7033&quot;&gt;RFC 7033&lt;/a&gt; describes the WebFinger protocol. &lt;a href=&quot;https://dans.land/garden/mastodon-alias#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;Mastodon uses the &lt;code&gt;acct:&lt;/code&gt; URI scheme as described in &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7565&quot;&gt;RFC 7565&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/mastodon-alias#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;The WebFinger RFC &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7033#section-10.2&quot;&gt;mentions&lt;/a&gt; that the &lt;code&gt;Content-Type&lt;/code&gt; of a WebFinger response should be &lt;code&gt;application/jrd+json&lt;/code&gt;. But it looks like using &lt;code&gt;application/json&lt;/code&gt; also works. &lt;a href=&quot;https://dans.land/garden/mastodon-alias#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-4&quot;&gt;
&lt;p&gt;Sadly, using emoji didn’t work though. &lt;a href=&quot;https://dans.land/garden/mastodon-alias#user-content-fnref-4&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>HTTP handlers</title><link>https://dans.land/garden/go/http-handlers</link><guid isPermaLink="true">https://dans.land/garden/go/http-handlers</guid><description>Learning about the HTTP request multiplexer, handlers and middleware in Go.</description><pubDate>Thu, 22 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently had to hook up some middleware in a Go service.
While looking into the Go standard library &lt;a href=&quot;https://pkg.go.dev/net/http&quot;&gt;net/http&lt;/a&gt; package, I got a bit confused by all the different (but similarly named) types and functions that deal with HTTP handlers.&lt;/p&gt;
&lt;p&gt;For example, the &lt;code&gt;http.Handler&lt;/code&gt; and &lt;code&gt;http.HandlerFunc&lt;/code&gt; types,
the &lt;code&gt;http.Handle()&lt;/code&gt; and &lt;code&gt;http.HandleFunc()&lt;/code&gt; functions,
and the &lt;code&gt;http.ServeMux&lt;/code&gt; type that &lt;em&gt;also&lt;/em&gt; defines &lt;code&gt;Handle()&lt;/code&gt; and &lt;code&gt;HandleFunc()&lt;/code&gt; methods.&lt;/p&gt;
&lt;p&gt;At first I didn’t really get the difference.
Nor did I understand why middleware in Go is typically a function that accepts and returns an &lt;code&gt;http.Handler&lt;/code&gt;.
But after some (re)reading and experimentation, it all made sense.
This is what I learned.&lt;/p&gt;
&lt;h2 id=&quot;handler--servemux&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#handler--servemux&quot;&gt;Handler &amp;amp; ServeMux&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In a web server we’d typically have &lt;em&gt;handlers&lt;/em&gt; that respond to HTTP requests, and &lt;em&gt;routers&lt;/em&gt; that map URL patterns to handlers.
But how are these exposed via the standard library?&lt;/p&gt;
&lt;h3 id=&quot;handler&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#handler&quot;&gt;Handler&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;net/http&lt;/code&gt; package exposes the &lt;a href=&quot;https://pkg.go.dev/net/http#Handler&quot;&gt;http.Handler&lt;/a&gt; interface:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Handler interface {
  ServeHTTP(ResponseWriter, *Request)
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Any type that satisfies the &lt;code&gt;http.Handler&lt;/code&gt; interface can be used as a handler.
Or in other words, any type that implements the &lt;code&gt;ServeHTTP(ResponseWriter, *Request)&lt;/code&gt; method can be used to respond to HTTP requests.&lt;/p&gt;
&lt;h3 id=&quot;servemux&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#servemux&quot;&gt;ServeMux&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As far as I know, the standard library doesn’t use the term “router”.
It uses the term &lt;em&gt;HTTP request multiplexer&lt;/em&gt; instead.
But they are essentially the same thing.&lt;/p&gt;
&lt;p&gt;The multiplexer matches the URL path of an incoming request against registered patterns, and calls the handler for the pattern that most closely matches the URL.
The standard library exposes &lt;a href=&quot;https://pkg.go.dev/net/http#ServeMux&quot;&gt;http.ServeMux&lt;/a&gt; for this purpose.&lt;/p&gt;
&lt;p&gt;So if we implement an &lt;code&gt;http.Handler&lt;/code&gt; and use it together with an &lt;code&gt;http.ServeMux&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/go/http-handlers#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, we can use &lt;a href=&quot;https://pkg.go.dev/net/http#ServeMux.Handle&quot;&gt;Handle()&lt;/a&gt; to respond to HTTP requests:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;main.go&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main

import (
  &quot;log&quot;
  &quot;net/http&quot;
)

type HomeHandler struct{}

func (h HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte(&quot;Home&quot;))
}

func main() {
  mux := http.NewServeMux()
  handler := HomeHandler{}
  mux.Handle(&quot;/&quot;, handler)
  if err := http.ListenAndServe(&quot;:8888&quot;, mux); err != nil {
    log.Fatal(err)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;handle-vs-handlefunc&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#handle-vs-handlefunc&quot;&gt;Handle vs HandleFunc&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the example above we used the &lt;code&gt;Handle()&lt;/code&gt; method to respond to requests.
But &lt;code&gt;http.ServeMux&lt;/code&gt; also has the &lt;a href=&quot;https://pkg.go.dev/net/http#ServeMux.HandleFunc&quot;&gt;HandleFunc()&lt;/a&gt; method.
So what’s the difference?&lt;/p&gt;
&lt;p&gt;At first glance it looks like both accept a pattern and a handler.
But &lt;code&gt;Handle()&lt;/code&gt; requires a handler that satisfies the &lt;code&gt;http.Handler&lt;/code&gt; interface,
while &lt;code&gt;HandleFunc()&lt;/code&gt; accepts any function that defines &lt;code&gt;http.ResponseWriter&lt;/code&gt; and &lt;code&gt;*http.Request&lt;/code&gt; parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Handle(pattern string, handler Handler)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HandleFunc(pattern string, handler func(ResponseWriter, *Request))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we can achieve the exact same thing as in the example above with the following:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;main.go&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main

import (
  &quot;log&quot;
  &quot;net/http&quot;
)

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(&quot;Home&quot;))
  })
  if err := http.ListenAndServe(&quot;:8888&quot;, mux); err != nil {
    log.Fatal(err)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;defaultservemux&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#defaultservemux&quot;&gt;DefaultServeMux&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We saw in the above examples that &lt;code&gt;http.ServeMux&lt;/code&gt; exposes the &lt;code&gt;Handle()&lt;/code&gt; and &lt;code&gt;HandleFunc()&lt;/code&gt; methods.
But it turns out that instead of first creating a multiplexer with &lt;code&gt;http.NewServeMux()&lt;/code&gt;, it’s also possible to just use &lt;a href=&quot;https://pkg.go.dev/net/http#Handle&quot;&gt;http.Handle()&lt;/a&gt; or &lt;a href=&quot;https://pkg.go.dev/net/http#HandleFunc&quot;&gt;http.HandleFunc()&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;main.go&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main

import (
  &quot;log&quot;
  &quot;net/http&quot;
)

func main() {
  http.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(&quot;Home&quot;))
  })
  if err := http.ListenAndServe(&quot;:8888&quot;, nil); err != nil {
    log.Fatal(err)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Using these functions will actually make use of a “default” &lt;code&gt;http.ServeMux&lt;/code&gt; under the hood.
This default multiplexer is defined by the standard library, and named &lt;a href=&quot;https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/net/http/server.go;l=2552&quot;&gt;DefaultServeMux&lt;/a&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/go/http-handlers#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&quot;so-whats-handlerfunc&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#so-whats-handlerfunc&quot;&gt;So what’s HandlerFunc?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Turns out that a very useful type to know about when working with handlers is &lt;a href=&quot;https://pkg.go.dev/net/http#HandlerFunc&quot;&gt;http.HandlerFunc&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This type allows us to convert a “plain” handler function (i.e. &lt;code&gt;func(ResponseWriter, *Request)&lt;/code&gt;) into a “real” &lt;code&gt;http.Handler&lt;/code&gt;.
This is great because it makes working with handlers more convenient.&lt;/p&gt;
&lt;p&gt;So the following won’t compile:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;handler := func(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte(&quot;Home&quot;))
}
http.Handle(&quot;/&quot;, handler) // Does not compile&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;But this will compile:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;handler := func(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte(&quot;Home&quot;))
}
http.Handle(&quot;/&quot;, http.HandlerFunc(handler)) // Compiles&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Note that &lt;code&gt;http.HandlerFunc(handler)&lt;/code&gt; does &lt;em&gt;not&lt;/em&gt; invoke &lt;code&gt;http.HandlerFunc&lt;/code&gt; (it’s a type, not a function!).
But it’s a &lt;a href=&quot;https://go.dev/ref/spec#Conversions&quot;&gt;type conversion&lt;/a&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/go/http-handlers#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; which converts &lt;code&gt;handler&lt;/code&gt; with type &lt;code&gt;func(ResponseWriter, *Request)&lt;/code&gt; into type &lt;code&gt;http.HandlerFunc&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;middleware&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#middleware&quot;&gt;Middleware&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Middleware typically consists of small functions that take a request, do something with it, and then pass it to &lt;em&gt;another&lt;/em&gt; middleware function or the (final) handler.&lt;/p&gt;
&lt;p&gt;In Go, middleware will sit “between” the multiplexer and the handler responding to the HTTP requests.&lt;/p&gt;
&lt;p&gt;A few examples of typical middleware use cases are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logging requests.&lt;/li&gt;
&lt;li&gt;Auth (i.e. authenticate and/or authorize requests).&lt;/li&gt;
&lt;li&gt;Header and response manipulation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Generally speaking, in Go, functions that accept and return an &lt;code&gt;http.Handler&lt;/code&gt; are considered middleware:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func(next http.Handler) http.Handler&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func someMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Do something with r...

    next.ServeHTTP(w, r)
  })
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Why does middleware accept and return an &lt;code&gt;http.Handler&lt;/code&gt;? This allows us to create a “chain” of handlers:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;http.Handle(&quot;/&quot;, middlewareA(middlewareB(middlewareC(handler))))&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;But this can get a bit unreadable, which is why third-party libraries typically offer a &lt;code&gt;Use()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;For example, this is how you’d use it with &lt;a href=&quot;https://go-chi.io/#/pages/middleware&quot;&gt;chi&lt;/a&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;r := chi.NewRouter()
r.Use(middlewareA, middlewareB, middlewareC)
r.Get(&quot;/&quot;, handler)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;servemux-gotchas&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#servemux-gotchas&quot;&gt;ServeMux gotchas&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To wrap up, I want to highlight some (sometimes unexpected) behavior I learned about while reading the docs and playing with &lt;code&gt;http.ServeMux&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;paths-and-patterns&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#paths-and-patterns&quot;&gt;Paths and patterns&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Go 1.22 routing enhancements&lt;/p&gt;
&lt;p&gt;This section focuses on fixed and subtree path patterns.
Since Go 1.22, &lt;code&gt;http.ServeMux&lt;/code&gt; also supports &lt;a href=&quot;https://go.dev/blog/routing-enhancements&quot;&gt;method-specific patterns and path wildcards&lt;/a&gt;.
Captured wildcard values are available through &lt;code&gt;Request.PathValue&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The simplest patterns name either &lt;strong&gt;fixed paths&lt;/strong&gt; or &lt;strong&gt;subtree paths&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Fixed paths do &lt;em&gt;not&lt;/em&gt; have a trailing slash (e.g. &lt;code&gt;/blog&lt;/code&gt; or &lt;code&gt;/blog/create&lt;/code&gt;).
They are only matched when the URL &lt;em&gt;exactly&lt;/em&gt; matches the pattern.&lt;/p&gt;
&lt;p&gt;Subtree paths &lt;em&gt;do&lt;/em&gt; have a trailing slash (e.g. &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;/blog/&lt;/code&gt; or &lt;code&gt;/blog/create/&lt;/code&gt;).
They match all paths &lt;em&gt;not&lt;/em&gt; matched by other registered patterns.
So subtree paths kind of work like “catch all” patterns:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;mux.HandleFunc(&quot;/&quot;, homeHandler) // Subtree path&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Request path&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Calls &lt;code&gt;homeHandler&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog/&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog/create&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/notfound&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Note that subtree path patterns will match when &lt;em&gt;not&lt;/em&gt; matched by other registered (fixed path) patterns:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;mux.HandleFunc(&quot;/&quot;, homeHandler) // Subtree path
mux.HandleFunc(&quot;/blog&quot;, blogHandler) // Fixed path&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Request path&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Calls &lt;code&gt;homeHandler&lt;/code&gt;&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Calls &lt;code&gt;blogHandler&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog/&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog/create&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/notfound&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;So to for example let handlers match the &lt;code&gt;/blog/*&lt;/code&gt; URL patterns, a subtree path must be used instead of a fixed path:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;mux.HandleFunc(&quot;/&quot;, homeHandler) // Subtree path
mux.HandleFunc(&quot;/blog/&quot;, blogHandler) // Subtree path&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Request path&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Calls &lt;code&gt;homeHandler&lt;/code&gt;&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Calls &lt;code&gt;blogHandler&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No (redirects to &lt;code&gt;/blog/&lt;/code&gt;)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog/&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog/create&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/notfound&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Also note that longer registered path patterns take precedence over shorter ones:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;mux.HandleFunc(&quot;/blog/&quot;, blogHandler) // Subtree path
mux.HandleFunc(&quot;/blog/create/&quot;, blogCreateHandler) // Subtree path&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Request path&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Calls &lt;code&gt;blogHandler&lt;/code&gt;&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Calls &lt;code&gt;blogCreateHandler&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No (redirects to &lt;code&gt;/blog/&lt;/code&gt;)&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog/&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog/1&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog/create&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No (redirects to &lt;code&gt;/blog/create/&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/blog/create/1&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/notfound&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Yes&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;path-redirects&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#path-redirects&quot;&gt;Path redirects&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If a subtree path pattern has been registered with &lt;code&gt;http.ServeMux&lt;/code&gt;,
and it receives a request path &lt;em&gt;without&lt;/em&gt; a trailing slash,
it will redirect the request to the “subtree root” (i.e. redirect to the request path &lt;em&gt;with&lt;/em&gt; the trailing slash).&lt;/p&gt;
&lt;p&gt;To prevent this from happening you need to register the pattern for the path &lt;em&gt;without&lt;/em&gt; the trailing slash.&lt;/p&gt;
&lt;p&gt;For example, when registering &lt;code&gt;/blog/&lt;/code&gt;, a request to &lt;code&gt;/blog&lt;/code&gt; will redirect to &lt;code&gt;/blog/&lt;/code&gt;, &lt;em&gt;unless&lt;/em&gt; &lt;code&gt;/blog&lt;/code&gt; is also registered.&lt;/p&gt;
&lt;h3 id=&quot;sanitization&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#sanitization&quot;&gt;Sanitization&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;http.ServeMux&lt;/code&gt; will “sanitize” the URL request path and the Host header.&lt;/p&gt;
&lt;p&gt;It will strip the port number and redirect any request containing &lt;code&gt;.&lt;/code&gt; or &lt;code&gt;..&lt;/code&gt; elements, or repeated slashes, to a similar but cleaner URL.&lt;/p&gt;
&lt;h3 id=&quot;limitations&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#limitations&quot;&gt;Limitations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;http.ServeMux&lt;/code&gt; supports fixed paths, subtree paths, method patterns and path wildcards.
It does &lt;em&gt;not&lt;/em&gt; support regular-expression path patterns.&lt;/p&gt;
&lt;p&gt;For regular-expression patterns, you either need to implement that yourself (e.g. check the path in a handler),
or use a third-party library like &lt;a href=&quot;https://github.com/go-chi/chi&quot;&gt;chi&lt;/a&gt; or &lt;a href=&quot;https://github.com/gin-gonic/gin&quot;&gt;gin&lt;/a&gt;.&lt;/p&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 id=&quot;footnote-label&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/go/http-handlers#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;&lt;code&gt;http.ServeMux&lt;/code&gt; also satisfies the &lt;code&gt;http.Handler&lt;/code&gt; interface, as it implements a &lt;a href=&quot;https://pkg.go.dev/net/http#ServeMux.ServeHTTP&quot;&gt;ServeHTTP(ResponseWriter, *Request)&lt;/a&gt; method. &lt;a href=&quot;https://dans.land/garden/go/http-handlers#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;&lt;code&gt;DefaultServeMux&lt;/code&gt; is just a &lt;a href=&quot;https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/net/http/server.go;drc=867babe1b1587ab6961c1d6274be2426e90bf5d4;l=2305&quot;&gt;ServeMux&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/go/http-handlers#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;A type conversion is &lt;em&gt;not&lt;/em&gt; the same thing as a &lt;a href=&quot;https://go.dev/ref/spec#Type_assertions&quot;&gt;type assertion&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/go/http-handlers#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>Audio transcoding</title><link>https://dans.land/garden/lambda/audio-transcoding</link><guid isPermaLink="true">https://dans.land/garden/lambda/audio-transcoding</guid><description>Transcoding short audio files with AWS Lambda, Amazon Elastic Transcoder or FFmpeg.</description><pubDate>Sun, 27 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Historical walkthrough&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/blogs/media/support-for-amazon-elastic-transcoder-ending-soon/&quot;&gt;Elastic Transcoder shut down on 13 November 2025&lt;/a&gt;.
The Node.js 10 runtime, Serverless 1 configuration and AWS SDK v2 examples below also need migration before use today.
Prices, limits and billing increments refer to the original tests.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For a side project I’m converting WebM audio files to MP3.
I initially started doing this with &lt;a href=&quot;https://aws.amazon.com/blogs/aws/audio-support-for-the-amazon-elastic-transcoder/&quot;&gt;Amazon Elastic Transcoder&lt;/a&gt;.
But after doing the same with &lt;a href=&quot;https://www.ffmpeg.org/&quot;&gt;FFmpeg&lt;/a&gt; and &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html&quot;&gt;Lambda Layers&lt;/a&gt;,
my testing showed that the latter was &lt;strong&gt;substantially cheaper and ~2 times faster for short audio&lt;/strong&gt; recordings (for ~3 minute, ~3 MB files).&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Just want to read the code?&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/upstandfm/audio-transcoder&quot;&gt;github.com/upstandfm/audio-transcoder&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;use-case&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#use-case&quot;&gt;Use case&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My &lt;a href=&quot;https://github.com/upstandfm/app&quot;&gt;side project&lt;/a&gt; is a web app that allows users to record their voice so others can listen to it.
In the app I use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API&quot;&gt;MediaStream Recording API&lt;/a&gt; (aka Media Recording API) to easily record audio from the user’s input device.
It works really well, and you don’t have to use any external libraries!&lt;/p&gt;
&lt;p&gt;There’s one catch though.
At the time of this writing it only works in Firefox, Chrome and Opera.
It “sort of” works in Safari&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.
Even though that’s a bit disappointing, I’m okay with that for my use case.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;Safari has since &lt;a href=&quot;https://webkit.org/blog/11353/mediarecorder-api/&quot;&gt;enabled MediaRecorder by default&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So after I had built something functional that allowed me to record my voice, it turned out that the audio file I ended up with had to be &lt;em&gt;transcoded&lt;/em&gt; if I wanted to listen to it across a wide range of browsers and devices.&lt;/p&gt;
&lt;h2 id=&quot;what-does-transcoding-even-mean&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#what-does-transcoding-even-mean&quot;&gt;What does transcoding even mean?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before I can answer that, we need to explore &lt;em&gt;what&lt;/em&gt; an audio file is.&lt;/p&gt;
&lt;p&gt;We can think of an audio file like a stream of data elements wrapped in a container.
This container is formally called a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers&quot;&gt;media container format&lt;/a&gt;.
It’s basically a &lt;em&gt;file format&lt;/em&gt; (think file type) that can store different types of data elements (i.e. bits).&lt;/p&gt;
&lt;p&gt;The container describes how this data “coexists” in a file.
Some container formats only support audio, like &lt;a href=&quot;https://en.wikipedia.org/wiki/WAV&quot;&gt;WAVE&lt;/a&gt; (usually referred to as WAV).
Others support both audio and video, like &lt;a href=&quot;https://www.webmproject.org/&quot;&gt;WebM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So a container “wraps” data to store it in a file, but information can be stored in different ways.
We’ll also want to &lt;em&gt;compress&lt;/em&gt; the data to optimize for storage and/or bandwidth by &lt;em&gt;encoding&lt;/em&gt; it (i.e. converting it from one “form” to another).&lt;/p&gt;
&lt;p&gt;This is where a &lt;em&gt;codec&lt;/em&gt; (&lt;strong&gt;co&lt;/strong&gt;der/&lt;strong&gt;dec&lt;/strong&gt;oder) comes into play.
It handles all the processing that’s required to &lt;em&gt;encode&lt;/em&gt; (compress) and &lt;em&gt;decode&lt;/em&gt; (decompress) the audio data.&lt;/p&gt;
&lt;p&gt;So when describing an audio or video file, we usually need to specify both its container and its codec&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.
For example, AAC audio in an MP4 container is commonly stored as an &lt;code&gt;.m4a&lt;/code&gt; file.
An &lt;a href=&quot;https://en.wikipedia.org/wiki/MP3&quot;&gt;MP3&lt;/a&gt; file contains MPEG-1 or MPEG-2 Audio Layer III frames:
it does not need an MP4 container.&lt;/p&gt;
&lt;p&gt;So what does transcoding mean?
It’s the process of converting one encoding into another.
If we convert one container format into another, this process is called &lt;em&gt;transmuxing&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;There are a lot of codecs available.
Each codec will have a different effect on the quality, size and/or compatibility of the audio file&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-do-you-need-to-transcode-audio&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#why-do-you-need-to-transcode-audio&quot;&gt;Why do you need to transcode audio?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You might be wondering (like I was), if we can record audio directly in the browser and immediately use the result in our app, why do we even have to transcode it?&lt;/p&gt;
&lt;p&gt;The answer is: to optimize for &lt;em&gt;compatibility&lt;/em&gt;.
Because the Media Recording API can &lt;em&gt;not&lt;/em&gt; record audio in all media formats.&lt;/p&gt;
&lt;p&gt;For example, MP3 has good compatibility across browsers and devices for playback, but is &lt;em&gt;not&lt;/em&gt; supported by the Media Recording API.
What formats are supported depend on the browser’s specific implementation of said API.&lt;/p&gt;
&lt;p&gt;We can use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/isTypeSupported&quot;&gt;isTypeSupported&lt;/a&gt; method to figure out if we can record in a specific media type by calling it with a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types&quot;&gt;MIME&lt;/a&gt; type.
Run the following code in the web console (e.g. in Firefox) to see it in action:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;MediaRecorder.isTypeSupported(&quot;audio/mpeg&quot;) // false&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Okay, MP3 isn’t supported. Which format can we use to record in then? It looks like WebM is a good choice:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;MediaRecorder.isTypeSupported(&quot;audio/webm&quot;) // true&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Also note that you can specify the codec in addition to the container:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;MediaRecorder.isTypeSupported(&quot;audio/webm;codecs=opus&quot;) // true&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;So if we want to end up with MP3 files of the recordings, we need to transcode (and technically also transmux) the WebM audio recordings.&lt;/p&gt;
&lt;h3 id=&quot;how-will-we-do-this&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#how-will-we-do-this&quot;&gt;How will we do this?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We’ll explore two implementations that both convert a WebM audio file to MP3:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#using-amazon-elastic-transcoder&quot;&gt;Using Amazon Elastic Transcoder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#using-ffmpeg-and-lambda-layers&quot;&gt;Using FFmpeg and Lambda Layers&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For both implementations we’ll use the &lt;a href=&quot;https://serverless.com/&quot;&gt;Serverless Framework&lt;/a&gt; and &lt;a href=&quot;https://nodejs.org/en&quot;&gt;Node.js&lt;/a&gt; to write the code for the &lt;a href=&quot;https://aws.amazon.com/lambda&quot;&gt;Lambda&lt;/a&gt; function that converts an audio file.&lt;/p&gt;
&lt;p&gt;Before we get started, make sure you have Node.js installed.
Then use &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npm&lt;/a&gt; to install the Serverless Framework globally:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm i -g serverless&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Additionally, we’ll need two &lt;a href=&quot;https://aws.amazon.com/s3&quot;&gt;S3&lt;/a&gt; buckets to process and store the converted audio files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;em&gt;input&lt;/em&gt; bucket to upload WebM audio files.&lt;/li&gt;
&lt;li&gt;An &lt;em&gt;output&lt;/em&gt; bucket to store transcoded MP3 files.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;using-amazon-elastic-transcoder&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#using-amazon-elastic-transcoder&quot;&gt;Using Amazon Elastic Transcoder&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Amazon Elastic Transcoder is a fully managed and highly scalable AWS service that can be used to transcode audio and video files.&lt;/p&gt;
&lt;p&gt;We can use this service to schedule a transcoding job in a pipeline.
The pipeline knows from which bucket to read a file that needs to be converted, and to which bucket the converted file should be written.
The job contains instructions on which file to transcode, and to what format it should be converted.&lt;/p&gt;
&lt;p&gt;We’ll create a Lambda function that will “listen” to the S3 input bucket.
Whenever a new object is created in that bucket, Lambda will schedule a transcoder job to create the MP3 file.&lt;/p&gt;
&lt;p&gt;So the flow will be like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A WebM audio file is uploaded to the input bucket.&lt;/li&gt;
&lt;li&gt;The Lambda function is triggered, and uses the key of the created S3 object to schedule a transcoder job.&lt;/li&gt;
&lt;li&gt;A job is scheduled in the pipeline, where Amazon Elastic Transcoder:
&lt;ul&gt;
&lt;li&gt;Fetches the WebM audio file from the input bucket.&lt;/li&gt;
&lt;li&gt;Transcodes the WebM audio file to MP3.&lt;/li&gt;
&lt;li&gt;Uploads the MP3 file to the output bucket.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Warning&lt;/p&gt;
&lt;p&gt;At the time of this writing &lt;a href=&quot;https://aws.amazon.com/cloudformation&quot;&gt;AWS CloudFormation&lt;/a&gt; has &lt;strong&gt;no&lt;/strong&gt; support for Amazon Elastic Transcoder. So you’ll have to use the AWS web console to create and configure your pipeline(s).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We’ll go through the following steps to get it up and running:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#1-create-a-pipeline&quot;&gt;1. Create a pipeline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#2-choose-a-preset&quot;&gt;2. Choose a preset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#3-create-an-iam-policy&quot;&gt;3. Create an IAM Policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#4-create-a-serverless-project&quot;&gt;4. Create a Serverless project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#5-implement-the-lambda-function&quot;&gt;5. Implement the Lambda function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#6-release-the-lambda-function&quot;&gt;6. Release the Lambda function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#7-schedule-a-job&quot;&gt;7. Schedule a job&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-create-a-pipeline&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#1-create-a-pipeline&quot;&gt;1. Create a pipeline&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Navigate to the Elastic Transcoder service in the AWS web console.
Select a region (we’ll use &lt;code&gt;eu-west-1&lt;/code&gt;) and click on “Create New Pipeline”.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Elastic Transcoder pipeline configuration&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;3416&quot; height=&quot;1991&quot; src=&quot;https://dans.land/assets/create-pipeline.CxmQJNpc_Z4wPBF.webp&quot; srcset=&quot;https://dans.land/assets/create-pipeline.CxmQJNpc_sU42I.webp 320w, https://dans.land/assets/create-pipeline.CxmQJNpc_1wUDkn.webp 480w, https://dans.land/assets/create-pipeline.CxmQJNpc_ZT9See.webp 672w, https://dans.land/assets/create-pipeline.CxmQJNpc_ZcTW3v.webp 960w, https://dans.land/assets/create-pipeline.CxmQJNpc_Z1UESmq.webp 1344w, https://dans.land/assets/create-pipeline.CxmQJNpc_Z4wPBF.webp 3416w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Elastic Transcoder pipeline configuration&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Create the pipeline and take note of the ARN and Pipeline ID.
We’ll need both to configure the Lambda function later on.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Elastic Transcoder pipeline ARN and ID&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;3408&quot; height=&quot;1990&quot; src=&quot;https://dans.land/assets/created-pipeline.DgCU7QSF_14RC1S.webp&quot; srcset=&quot;https://dans.land/assets/created-pipeline.DgCU7QSF_Z2lJMiM.webp 320w, https://dans.land/assets/created-pipeline.DgCU7QSF_Z1QpbYE.webp 480w, https://dans.land/assets/created-pipeline.DgCU7QSF_Z2pKaix.webp 672w, https://dans.land/assets/created-pipeline.DgCU7QSF_Z29jfO5.webp 960w, https://dans.land/assets/created-pipeline.DgCU7QSF_ZiXenT.webp 1344w, https://dans.land/assets/created-pipeline.DgCU7QSF_14RC1S.webp 3408w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Elastic Transcoder pipeline ARN and ID&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;2-choose-a-preset&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#2-choose-a-preset&quot;&gt;2. Choose a preset&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The pipeline we created in the previous step requires a &lt;a href=&quot;https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ElasticTranscoder.html#readPreset-property&quot;&gt;preset&lt;/a&gt; to work.
Presets contain settings we want to be applied during the transcoding process.
Lucky for us, AWS already has system presets to convert to MP3 files.&lt;/p&gt;
&lt;p&gt;In the web console, click on “Presets” and filter on the keyword “MP3”.
Select one and take note of its ARN and Preset ID.
We’ll also need these to configure the Lambda function.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;MP3 preset ARN and ID&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;3412&quot; height=&quot;1994&quot; src=&quot;https://dans.land/assets/preset.FBXTcazW_Z1WbOaQ.webp&quot; srcset=&quot;https://dans.land/assets/preset.FBXTcazW_PfChj.webp 320w, https://dans.land/assets/preset.FBXTcazW_ZmGjsD.webp 480w, https://dans.land/assets/preset.FBXTcazW_1K6njO.webp 672w, https://dans.land/assets/preset.FBXTcazW_Z2qu8UM.webp 960w, https://dans.land/assets/preset.FBXTcazW_98ygC.webp 1344w, https://dans.land/assets/preset.FBXTcazW_Z1WbOaQ.webp 3412w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;MP3 preset ARN and ID&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;3-create-an-iam-policy&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#3-create-an-iam-policy&quot;&gt;3. Create an IAM Policy&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;AWS will already have created an IAM Role named &lt;code&gt;Elastic_Transcoder_Default_Role&lt;/code&gt;.
But in order for the pipeline to read objects from the input bucket and write objects to the output bucket,
we need to make sure the role has the required permissions to do so.&lt;/p&gt;
&lt;p&gt;Create a new IAM Policy with the following configuration:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;s3:GetObject&quot;,
      &quot;Resource&quot;: &quot;arn:aws:s3:::raw.recordings/*&quot;
    },
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;s3:PutObject&quot;,
      &quot;Resource&quot;: &quot;arn:aws:s3:::transcoded.recordings/*&quot;
    },
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: &quot;s3:ListBucket&quot;,
      &quot;Resource&quot;: &quot;arn:aws:s3:::transcoded.recordings&quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Make sure the resource ARNs of your input and output buckets are named correctly.
After the Policy has been created, attach it to &lt;code&gt;Elastic_Transcoder_Default_Role&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;4-create-a-serverless-project&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#4-create-a-serverless-project&quot;&gt;4. Create a Serverless project&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Create a new project named “audio-transcoder”.
Move into this directory and create a Serverless manifest in the project root:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/serverless.yml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: audio-transcoder

provider:
  name: aws
  runtime: nodejs10.x

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Add the Elastic Transcoder Pipeline ID, MP3 Preset ID and region (from &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#1-create-a-pipeline&quot;&gt;step 1&lt;/a&gt; and &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#2-choose-a-preset&quot;&gt;step 2&lt;/a&gt;) as environment variables:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/serverless.yml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: audio-transcoder

provider:
  name: aws
  runtime: nodejs10.x
  environment:
    TRANSCODE_AUDIO_PIPELINE_ID: &quot;1572538082044-xmgzaa&quot;
    TRANSCODER_MP3_PRESET_ID: &quot;1351620000001-300040&quot;
    ELASTIC_TRANSCODER_REGION: &quot;eu-west-1&quot;

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Use the Elastic Transcoder Pipeline ARN and MP3 Preset ARN (from &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#1-create-a-pipeline&quot;&gt;step 1&lt;/a&gt; and &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#2-choose-a-preset&quot;&gt;step 2&lt;/a&gt;) to configure the Lambda with the required IAM permissions, so it can create transcoder jobs:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/serverless.yml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: audio-transcoder

provider:
  name: aws
  runtime: nodejs10.x
  environment:
    TRANSCODE_AUDIO_PIPELINE_ID: &quot;1572538082044-xmgzaa&quot;
    TRANSCODER_MP3_PRESET_ID: &quot;1351620000001-300040&quot;
    ELASTIC_TRANSCODER_REGION: &quot;eu-west-1&quot;
  iamRoleStatements:
    - Effect: Allow
      Action:
        - elastictranscoder:CreateJob
      Resource:
        - YOUR_PIPELINE_ARN # Replace this with the ARN from step 1
        - YOUR_PRESET_ARN # Replace this with the ARN from step 2

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Finally, add the Lambda function definition.
This Lambda will be executed whenever an object is created in the input bucket:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/serverless.yml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: audio-transcoder

provider:
  name: aws
  runtime: nodejs10.x
  environment:
    TRANSCODE_AUDIO_PIPELINE_ID: &quot;1572538082044-xmgzaa&quot;
    TRANSCODER_MP3_PRESET_ID: &quot;1351620000001-300040&quot;
    ELASTIC_TRANSCODER_REGION: &quot;eu-west-1&quot;
  iamRoleStatements:
    - Effect: Allow
      Action:
        - elastictranscoder:CreateJob
      Resource:
        - YOUR_PIPELINE_ARN # Replace this with the ARN from step 1
        - YOUR_PRESET_ARN # Replace this with the ARN from step 2

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src

functions:
  transcodeToMp3:
    handler: src/handler.transcodeToMp3
    description: Transcode an audio file to MP3
    events:
      - s3:
          bucket: &quot;raw.recordings&quot;
          event: &quot;s3:ObjectCreated:*&quot;
          existing: true&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;5-implement-the-lambda-function&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#5-implement-the-lambda-function&quot;&gt;5. Implement the Lambda function&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In order to match the Lambda function definition in the Serverless manifest,
create a file named &lt;code&gt;handler.js&lt;/code&gt; in &lt;code&gt;src&lt;/code&gt;, and export a method named &lt;code&gt;transcodeToMp3&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/src/handler.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

module.exports.transcodeToMp3 = async () =&amp;gt; {
  try {
    // Implementation goes here.
  } catch (err) {
    console.log(&quot;Transcoder Error: &quot;, err)
    throw err
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;In the previous step we configured the Lambda to be executed whenever an object is created in the input bucket.
This means that AWS will call the Lambda with an &lt;code&gt;event&lt;/code&gt; message that contains a list of &lt;code&gt;Records&lt;/code&gt;.
Each &lt;code&gt;Record&lt;/code&gt; will contain an &lt;code&gt;s3&lt;/code&gt; object with information about the &lt;code&gt;s3:ObjectCreated&lt;/code&gt; event:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// &quot;event&quot; object:
{
  &quot;Records&quot;:[
    // &quot;Record&quot; object:
    {
      &quot;s3&quot;:{
        // Contains information about the &quot;s3:ObjectCreated&quot; event.
      }
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;s3&lt;/code&gt; object will contain a property called &lt;code&gt;key&lt;/code&gt;, which is the “name” of the file that was created in the input bucket.
For example, if we upload a file named &lt;code&gt;test.webm&lt;/code&gt; to the S3 bucket, the value of &lt;code&gt;key&lt;/code&gt; will be the (URL encoded!) string &lt;code&gt;test.webm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can see the entire event message structure in the &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/dev/notification-content-structure.html&quot;&gt;AWS S3 docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Also be aware that you can get &lt;strong&gt;more than one&lt;/strong&gt; &lt;code&gt;Record&lt;/code&gt;.
So always process all of them:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/src/handler.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

module.exports.transcodeToMp3 = async (event) =&amp;gt; {
  try {
    for (const Record of event.Records) {
      const { s3 } = Record
      if (!s3) {
        continue
      }

      const { object: s3Object = {} } = s3
      const { key } = s3Object
      if (!key) {
        continue
      }

      const decodedKey = decodeURIComponent(key.replace(/\+/g, &quot; &quot;))
      // TODO: use &quot;decodedKey&quot; to schedule transcoder job.
    }
  } catch (err) {
    console.log(&quot;Transcoder Error: &quot;, err)
    throw err
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Finally, initialize the transcoder client and schedule a transcoder job for every created object in the input bucket:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/src/handler.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const ElasticTranscoder = require(&quot;aws-sdk/clients/elastictranscoder&quot;)

const { ELASTIC_TRANSCODER_REGION, TRANSCODE_AUDIO_PIPELINE_ID, TRANSCODER_MP3_PRESET_ID } =
  process.env

const transcoderClient = new ElasticTranscoder({
  region: ELASTIC_TRANSCODER_REGION,
})

module.exports.transcodeToMp3 = async (event) =&amp;gt; {
  try {
    for (const Record of event.Records) {
      const { s3 } = Record
      if (!s3) {
        continue
      }

      const { object: s3Object = {} } = s3
      const { key } = s3Object
      if (!key) {
        continue
      }

      const decodedKey = decodeURIComponent(key.replace(/\+/g, &quot; &quot;))
      await transcoderClient
        .createJob({
          PipelineId: TRANSCODE_AUDIO_PIPELINE_ID,
          Input: {
            Key: decodedKey,
          },
          Outputs: [
            {
              Key: decodedKey.replace(/\.webm$/i, &quot;.mp3&quot;),
              PresetId: TRANSCODER_MP3_PRESET_ID,
            },
          ],
        })
        .promise()
    }
  } catch (err) {
    console.log(&quot;Transcoder Error: &quot;, err)
    throw err
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;You can read more about the &lt;code&gt;createJob&lt;/code&gt; API in the &lt;a href=&quot;https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ElasticTranscoder.html#createJob-property&quot;&gt;AWS JavaScript SDK&lt;/a&gt; docs.&lt;/p&gt;
&lt;h3 id=&quot;6-release-the-lambda-function&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#6-release-the-lambda-function&quot;&gt;6. Release the Lambda function&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In order to upload the Lambda to AWS, make sure you have your &lt;a href=&quot;https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html&quot;&gt;credentials configured&lt;/a&gt;.
Then run the following command from the project root to release the Lambda:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sls deploy --region eu-west-1 --stage prod&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;7-schedule-a-job&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#7-schedule-a-job&quot;&gt;7. Schedule a job&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With everything up and running, we can now upload a WebM audio file to the input bucket to schedule a transcoder job.
Navigate to the S3 service in the AWS web console:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select your input bucket.&lt;/li&gt;
&lt;li&gt;Click “Upload”.&lt;/li&gt;
&lt;li&gt;Add a WebM audio file.&lt;/li&gt;
&lt;li&gt;Click on “Upload” again.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This action will trigger an &lt;code&gt;s3:ObjectCreated&lt;/code&gt; event.
AWS will execute the Lambda function we deployed in the previous step, and it will schedule a transcoder job.&lt;/p&gt;
&lt;p&gt;To get more information about a scheduled job, navigate to the Elastic Transcoder service in the AWS web console.
Click on “Jobs”, select your pipeline and click “Search”.
Here you can select a job to get more details about it.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Completed WebM-to-MP3 transcoding job&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;3412&quot; height=&quot;1992&quot; src=&quot;https://dans.land/assets/created-job.BbZL_4Z0_Z31uPA.webp&quot; srcset=&quot;https://dans.land/assets/created-job.BbZL_4Z0_Z9nIYp.webp 320w, https://dans.land/assets/created-job.BbZL_4Z0_Z1lJEjW.webp 480w, https://dans.land/assets/created-job.BbZL_4Z0_Z1sdwP8.webp 672w, https://dans.land/assets/created-job.BbZL_4Z0_Zgoms0.webp 960w, https://dans.land/assets/created-job.BbZL_4Z0_1HlaR6.webp 1344w, https://dans.land/assets/created-job.BbZL_4Z0_Z31uPA.webp 3412w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Completed WebM-to-MP3 transcoding job&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If it has status “Complete”, there should be a file named &lt;code&gt;test.mp3&lt;/code&gt; in the output bucket!&lt;/p&gt;
&lt;h2 id=&quot;using-ffmpeg-and-lambda-layers&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#using-ffmpeg-and-lambda-layers&quot;&gt;Using FFmpeg and Lambda Layers&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;FFmpeg is a cross-platform solution that can be used to convert audio and video files.
Since it’s a binary, we’ll use a Lambda Layer to execute it from the Lambda function.&lt;/p&gt;
&lt;h3 id=&quot;whats-a-lambda-layer&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#whats-a-lambda-layer&quot;&gt;What’s a Lambda Layer?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Lambda Layers allow us to “pull in” extra dependencies into Lambda functions.
A layer is basically a ZIP archive that contains some code.
In order to use a layer we first must create and publish one.&lt;/p&gt;
&lt;p&gt;After we publish a layer we can configure any Lambda function to use it&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fn-4&quot; id=&quot;user-content-fnref-4&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;.
AWS will then extract the layer to a special directory called &lt;code&gt;/opt&lt;/code&gt;.
The Lambda function runtime will be able to execute it.&lt;/p&gt;
&lt;h3 id=&quot;how-different-is-this-implementation&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#how-different-is-this-implementation&quot;&gt;How different is this implementation?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We’re basically “swapping out” Amazon Elastic Transcoder with FFmpeg.
Other than that the flow is still the same.&lt;/p&gt;
&lt;p&gt;So since we’re still converting a WebM audio file to MP3 whenever it’s uploaded to the input bucket, we can reuse the Lambda from the &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#4-create-a-serverless-project&quot;&gt;previous implementation&lt;/a&gt; by making these changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace Amazon Elastic Transcoder with FFmpeg.&lt;/li&gt;
&lt;li&gt;Within the Lambda we will:
&lt;ul&gt;
&lt;li&gt;Retrieve the WebM audio file from the input bucket whenever it’s uploaded.&lt;/li&gt;
&lt;li&gt;Convert the retrieved WebM audio file to MP3 using FFmpeg.&lt;/li&gt;
&lt;li&gt;Write the converted MP3 file to the output bucket.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll apply these changes by going through the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#1-create-and-publish-ffmpeg-lambda-layer&quot;&gt;1. Create and publish FFmpeg Lambda Layer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#2-update-the-serverless-manifest&quot;&gt;2. Update the Serverless manifest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#3-update-the-lambda-function&quot;&gt;3. Update the Lambda function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#4-release-the-updated-lambda-function&quot;&gt;4. Release the updated Lambda function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#5-upload-another-webm-audio-file&quot;&gt;5. Upload another WebM audio file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#6-optimize-the-lambda-function&quot;&gt;6. Optimize the Lambda function&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-create-and-publish-ffmpeg-lambda-layer&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#1-create-and-publish-ffmpeg-lambda-layer&quot;&gt;1. Create and publish FFmpeg Lambda Layer&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Serverless Framework makes it very easy to work with layers.
To get started create a new project named “lambda-layers”.
Move into this directory and create a Serverless manifest in the project root:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-layers/serverless.yml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: lambda-layers

provider:
  name: aws
  runtime: nodejs10.x

package:
  exclude:
    - ./*
  include:
    - layers

layers:
  ffmpeg:
    path: layers
    description: FFmpeg binary
    compatibleRuntimes:
      - nodejs10.x
    licenseInfo: GPL v2+, for more info see https://github.com/FFmpeg/FFmpeg/blob/master/LICENSE.md&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The layer is named &lt;code&gt;ffmpeg&lt;/code&gt; and the &lt;code&gt;path&lt;/code&gt; property dictates that the layer code will reside in a directory named &lt;code&gt;layers&lt;/code&gt;.
Match this structure in the project by creating that directory first.&lt;/p&gt;
&lt;p&gt;Move into the &lt;code&gt;layers&lt;/code&gt; directory and download a static build of FFmpeg from &lt;a href=&quot;https://johnvansickle.com/ffmpeg&quot;&gt;johnvansickle.com/ffmpeg&lt;/a&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fn-5&quot; id=&quot;user-content-fnref-5&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Use the recommended &lt;code&gt;ffmpeg-git-amd64-static.tar.xz&lt;/code&gt; master build:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;curl -O https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Extract the files from the downloaded archive:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;tar -xvf ffmpeg-git-amd64-static.tar.xz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Remove the downloaded archive:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;rm ffmpeg-git-amd64-static.tar.xz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Rename the extracted directory to &lt;code&gt;ffmpeg&lt;/code&gt;, so it matches the configured layer name in the Serverless manifest. For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;mv ffmpeg-git-20191029-amd64-static ffmpeg&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;You should now have the following files and folder structure:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;lambda-layers
  ├── layers
  │   └── ffmpeg
  │       ├── GPLv3.txt
  │       ├── ffmpeg
  │       ├── ffprobe
  │       ├── manpages
  │       ├── model
  │       ├── qt-faststart
  │       └── readme.txt
  └── serverless.yml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Publish the layer by running the following command from the project root:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sls deploy --region eu-west-1 --stage prod&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;When Serverless finishes deploying, navigate to the Lambda service in the AWS web console and click on “Layers”.
Here you should see the published layer.
Click on it and take note of the ARN.
We’ll need it in the next step.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Published FFmpeg Lambda layer ARN&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2890&quot; height=&quot;1910&quot; src=&quot;https://dans.land/assets/published-layer.BFN7m01d_Z1E7SqJ.webp&quot; srcset=&quot;https://dans.land/assets/published-layer.BFN7m01d_Z18gkkl.webp 320w, https://dans.land/assets/published-layer.BFN7m01d_1GQzUQ.webp 480w, https://dans.land/assets/published-layer.BFN7m01d_Z1bfqOJ.webp 672w, https://dans.land/assets/published-layer.BFN7m01d_Z2jXWjw.webp 960w, https://dans.land/assets/published-layer.BFN7m01d_1FmJc.webp 1344w, https://dans.land/assets/published-layer.BFN7m01d_Z1E7SqJ.webp 2890w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Published FFmpeg Lambda layer ARN&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;2-update-the-serverless-manifest&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#2-update-the-serverless-manifest&quot;&gt;2. Update the Serverless manifest&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We’ll now be modifying the manifest file of the &lt;code&gt;audio-transcoder&lt;/code&gt; project.&lt;/p&gt;
&lt;p&gt;First change the environment variables, and add the names of your input and output buckets.&lt;/p&gt;
&lt;p&gt;Then change the IAM permissions so the Lambda function can read from the input bucket and write to the output bucket.&lt;/p&gt;
&lt;p&gt;Finally, change the Lambda function to use the FFmpeg layer with the ARN from the previous step:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/serverless.yml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: audio-transcoder

provider:
  name: aws
  runtime: nodejs10.x
  environment:
    S3_INPUT_BUCKET_NAME: &quot;raw.recordings&quot;
    S3_OUTPUT_BUCKET_NAME: &quot;transcoded.recordings&quot;
  iamRoleStatements:
    - Effect: Allow
      Action:
        - s3:GetObject
      Resource: arn:aws:s3:::raw.recordings/*
    - Effect: Allow
      Action:
        - s3:PutObject
      Resource: arn:aws:s3:::transcoded.recordings/*

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src

functions:
  transcodeToMp3:
    handler: src/handler.transcodeToMp3
    description: Transcode an audio file to MP3
    events:
      - s3:
          bucket: &quot;raw.recordings&quot;
          event: &quot;s3:ObjectCreated:*&quot;
          existing: true
    layers:
      - YOUR_FFMPEG_LAYER_ARN # Replace this with the ARN from step 1&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;3-update-the-lambda-function&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#3-update-the-lambda-function&quot;&gt;3. Update the Lambda function&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Since we have to read from the input bucket and write to the output bucket, replace the Elastic Transcoder client with the S3 client.
Use the &lt;code&gt;decodedKey&lt;/code&gt; to get the WebM recording from the input bucket:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/src/handler.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const S3 = require(&quot;aws-sdk/clients/s3&quot;)
const { S3_INPUT_BUCKET_NAME, S3_OUTPUT_BUCKET_NAME } = process.env
const s3Client = new S3()

module.exports.transcodeToMp3 = async (event) =&amp;gt; {
  try {
    for (const Record of event.Records) {
      const { s3 } = Record
      if (!s3) {
        continue
      }

      const { object: s3Object = {} } = s3
      const { key } = s3Object
      if (!key) {
        continue
      }

      const decodedKey = decodeURIComponent(key.replace(/\+/g, &quot; &quot;))
      const webmRecording = await s3Client
        .getObject({
          Bucket: S3_INPUT_BUCKET_NAME,
          Key: decodedKey,
        })
        .promise()
    }
  } catch (err) {
    console.log(&quot;Transcoder Error: &quot;, err)
    throw err
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The S3 client returns an object that contains a &lt;code&gt;Body&lt;/code&gt; property.
The value of &lt;code&gt;Body&lt;/code&gt; is a blob, which we’ll feed to the FFmpeg layer and convert it to MP3.&lt;/p&gt;
&lt;p&gt;We’ll do this via a helper function that will spawn a &lt;a href=&quot;https://nodejs.org/api/child_process.html#child_processspawnsynccommand-args-options&quot;&gt;synchronous child process&lt;/a&gt; which allows us to execute the &lt;code&gt;ffmpeg&lt;/code&gt; “command” (provided by the FFmpeg layer):&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/src/ffmpeg.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const { spawnSync } = require(&quot;child_process&quot;)

module.exports = {
  convertWebmToMp3(webmBlob) {
    spawnSync(
      &quot;/opt/ffmpeg/ffmpeg&quot;, // &quot;/opt/:LAYER_NAME/:BINARY_NAME&quot;
      [
        // FFmpeg command arguments go here.
      ],
      { stdio: &quot;inherit&quot; },
    )

    // Rest of the implementation goes here.
  },
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;For this implementation, we’ll give &lt;code&gt;ffmpeg&lt;/code&gt; files to read and write.
We’ll use a “special” directory called &lt;code&gt;/tmp&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fn-6&quot; id=&quot;user-content-fnref-6&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; for this.&lt;/p&gt;
&lt;p&gt;First write the WebM blob to &lt;code&gt;/tmp&lt;/code&gt; so FFmpeg can read it.
Then tell it to write the produced MP3 file back to the same directory:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/src/ffmpeg.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const { spawnSync } = require(&quot;child_process&quot;)
const { writeFileSync } = require(&quot;fs&quot;)

module.exports = {
  convertWebmToMp3(webmBlob) {
    const now = Date.now()
    const input = `/tmp/${now}.webm`
    const output = `/tmp/${now}.mp3`

    writeFileSync(input, webmBlob)

    spawnSync(&quot;/opt/ffmpeg/ffmpeg&quot;, [&quot;-i&quot;, input, output], {
      stdio: &quot;inherit&quot;,
    })

    // TODO: cleanup and return MP3 blob.
  },
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Now check that FFmpeg succeeded, read the produced MP3 file, and return the MP3 blob.
Use a unique temporary directory and clean it in &lt;code&gt;finally&lt;/code&gt;, so failures also release the temporary files:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/src/ffmpeg.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const { spawnSync } = require(&quot;child_process&quot;)
const { mkdtempSync, readFileSync, writeFileSync, existsSync, unlinkSync, rmdirSync } =
  require(&quot;fs&quot;)

module.exports = {
  convertWebmToMp3(webmBlob) {
    const directory = mkdtempSync(&quot;/tmp/transcode-&quot;)
    const input = `${directory}/input.webm`
    const output = `${directory}/output.mp3`
    try {
      writeFileSync(input, webmBlob)

      const result = spawnSync(&quot;/opt/ffmpeg/ffmpeg&quot;, [&quot;-i&quot;, input, output], {
        stdio: &quot;inherit&quot;,
      })
      if (result.error) {
        throw result.error
      }
      if (result.status !== 0) {
        throw new Error(`FFmpeg failed: ${result.signal || result.status}`)
      }

      return readFileSync(output)
    } finally {
      for (const file of [input, output]) {
        if (existsSync(file)) unlinkSync(file)
      }
      rmdirSync(directory)
    }
  },
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Finally, use the MP3 blob in the handler to write it to the output bucket:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/src/handler.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const S3 = require(&quot;aws-sdk/clients/s3&quot;)
const ffmpeg = require(&quot;./ffmpeg&quot;)
const { S3_INPUT_BUCKET_NAME, S3_OUTPUT_BUCKET_NAME } = process.env
const s3Client = new S3()

module.exports.transcodeToMp3 = async (event) =&amp;gt; {
  try {
    for (const Record of event.Records) {
      const { s3 } = Record
      if (!s3) {
        continue
      }

      const { object: s3Object = {} } = s3
      const { key } = s3Object
      if (!key) {
        continue
      }

      const decodedKey = decodeURIComponent(key.replace(/\+/g, &quot; &quot;))
      const webmRecording = await s3Client
        .getObject({
          Bucket: S3_INPUT_BUCKET_NAME,
          Key: decodedKey,
        })
        .promise()

      const mp3Blob = ffmpeg.convertWebmToMp3(webmRecording.Body)
      await s3Client
        .putObject({
          Bucket: S3_OUTPUT_BUCKET_NAME,
          Key: decodedKey.replace(/\.webm$/i, &quot;.mp3&quot;),
          ContentType: &quot;audio/mpeg&quot;,
          Body: mp3Blob,
        })
        .promise()
    }
  } catch (err) {
    console.log(&quot;Transcoder Error: &quot;, err)
    throw err
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Rethrowing errors lets Lambda treat failed work as a failure and apply its &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/invocation-async-error-handling.html&quot;&gt;asynchronous retry behavior&lt;/a&gt;.
S3 events can be delivered more than once, and a retry can repeat records already processed by this handler.
A production version should make repeated processing safe, especially when scheduling a paid transcoding job.
The &lt;code&gt;finally&lt;/code&gt; block handles ordinary failures, but cannot run if Lambda terminates the invocation on timeout.&lt;/p&gt;
&lt;h3 id=&quot;4-release-the-updated-lambda-function&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#4-release-the-updated-lambda-function&quot;&gt;4. Release the updated Lambda function&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Run the same command like before from the project root to release the Lambda:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;sls deploy --region eu-west-1 --stage prod&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;5-upload-another-webm-audio-file&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#5-upload-another-webm-audio-file&quot;&gt;5. Upload another WebM audio file&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When Serverless is done deploying, upload another WebM audio file to the input bucket.&lt;/p&gt;
&lt;p&gt;But nothing happens… Where’s the MP3 file?&lt;/p&gt;
&lt;p&gt;Let’s find out why this is happening by checking the Lambda function’s log files in the AWS web console:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to the Lambda service.&lt;/li&gt;
&lt;li&gt;Click on the &lt;code&gt;audio-transcoder-prod-transcodeToMp3&lt;/code&gt; function.&lt;/li&gt;
&lt;li&gt;Click on the “Monitoring” tab.&lt;/li&gt;
&lt;li&gt;Click the “View logs in CloudWatch” button.&lt;/li&gt;
&lt;li&gt;Select the latest log group.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here you should see the logs of the Lambda function.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;CloudWatch logs for the timed-out FFmpeg invocation&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2972&quot; height=&quot;1392&quot; src=&quot;https://dans.land/assets/logs-timeout.BBvzhGUC_1feubh.webp&quot; srcset=&quot;https://dans.land/assets/logs-timeout.BBvzhGUC_13cNO3.webp 320w, https://dans.land/assets/logs-timeout.BBvzhGUC_Z1sJHpo.webp 480w, https://dans.land/assets/logs-timeout.BBvzhGUC_ZqHFqv.webp 672w, https://dans.land/assets/logs-timeout.BBvzhGUC_Z1j9DFo.webp 960w, https://dans.land/assets/logs-timeout.BBvzhGUC_Z1mCGPw.webp 1344w, https://dans.land/assets/logs-timeout.BBvzhGUC_1feubh.webp 2972w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;CloudWatch logs for the timed-out FFmpeg invocation&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The logs tell us that FFmpeg is executing (hooray!) but that it doesn’t complete (boo!).&lt;/p&gt;
&lt;p&gt;In the middle of the transcoding process the logs just say &lt;code&gt;END&lt;/code&gt;.
On the last line we see that the Lambda had a duration of &lt;code&gt;6006.17 ms&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What’s happening?
The Lambda function takes “too long” to finish executing.
This Serverless setup defaults to a timeout of 6 seconds&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fn-7&quot; id=&quot;user-content-fnref-7&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;:
AWS Lambda itself defaults to &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/configuration-timeout.html&quot;&gt;3 seconds&lt;/a&gt;.
After 6 seconds the Lambda function is still not done transcoding, so AWS &lt;em&gt;terminates&lt;/em&gt; it.&lt;/p&gt;
&lt;p&gt;How do we solve this? By optimizing the Lambda function!&lt;/p&gt;
&lt;h3 id=&quot;6-optimize-the-lambda-function&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#6-optimize-the-lambda-function&quot;&gt;6. Optimize the Lambda function&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First let’s just set the timeout to a larger value.
For example, 180 seconds.
This way we can see how long it would actually take to complete the transcoding process:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/serverless.yml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;functions:
  transcodeToMp3:
    timeout: 180&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Deploy again.
When Serverless is done, upload another WebM audio file, and check the logs.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;CloudWatch logs for the completed FFmpeg invocation&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2974&quot; height=&quot;1502&quot; src=&quot;https://dans.land/assets/logs-complete.DJnpOtJc_26NDqv.webp&quot; srcset=&quot;https://dans.land/assets/logs-complete.DJnpOtJc_Z2jB66L.webp 320w, https://dans.land/assets/logs-complete.DJnpOtJc_f1Uah.webp 480w, https://dans.land/assets/logs-complete.DJnpOtJc_Z2qb3nk.webp 672w, https://dans.land/assets/logs-complete.DJnpOtJc_ZLH6eF.webp 960w, https://dans.land/assets/logs-complete.DJnpOtJc_26nkp9.webp 1344w, https://dans.land/assets/logs-complete.DJnpOtJc_26NDqv.webp 2974w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;CloudWatch logs for the completed FFmpeg invocation&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This time we see FFmpeg completes the transcoding process and that the Lambda had a duration of &lt;code&gt;7221.95 ms&lt;/code&gt;.
If we check the output bucket now, we’ll see the MP3 file!&lt;/p&gt;
&lt;h4 id=&quot;optimizing-further&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#optimizing-further&quot;&gt;Optimizing further&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Transcoding the audio file in ~7 seconds isn’t bad. Actually, it’s very similar to Amazon Elastic Transcoder.
But we can do better.&lt;/p&gt;
&lt;p&gt;Something that’s very important when working with Lambda, is to &lt;em&gt;always&lt;/em&gt; performance tune your functions.
Or in other words, always make sure that a Lambda function has the &lt;em&gt;optimum&lt;/em&gt; memory size configured.&lt;/p&gt;
&lt;p&gt;This is important because when you choose a higher memory setting, AWS will also give you an equivalent resource boost (like CPU).
This can reduce the Lambda function’s runtime duration.
But cost depends on both allocated memory and billed duration, so faster execution only saves money if it outweighs the higher memory allocation.&lt;/p&gt;
&lt;p&gt;By default a Lambda function has a memory setting of 128 MB.
So lets increase it and compare results.
A good strategy is usually to keep doubling memory and measure the duration.
But for the sake of brevity, I’m jumping ahead to 2048 MB:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;audio-transcoder/serverless.yml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;functions:
  transcodeToMp3:
    memorySize: 2048&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Deploy again.
When Serverless is done, upload another WebM audio file and check the logs.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;CloudWatch logs after increasing Lambda memory to 2048 MB&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2982&quot; height=&quot;1512&quot; src=&quot;https://dans.land/assets/logs-double-memory.BY5J5hTH_Z1fkFOb.webp&quot; srcset=&quot;https://dans.land/assets/logs-double-memory.BY5J5hTH_2alKUr.webp 320w, https://dans.land/assets/logs-double-memory.BY5J5hTH_ATXUr.webp 480w, https://dans.land/assets/logs-double-memory.BY5J5hTH_1y55pn.webp 672w, https://dans.land/assets/logs-double-memory.BY5J5hTH_2bWu5M.webp 960w, https://dans.land/assets/logs-double-memory.BY5J5hTH_Z2qY79b.webp 1344w, https://dans.land/assets/logs-double-memory.BY5J5hTH_Z1fkFOb.webp 2982w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;CloudWatch logs after increasing Lambda memory to 2048 MB&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Great, it’s even faster now!
Does this mean we can just keep increasing the memory and reap the benefits?
Sadly, no.
There’s a tipping point where increasing the memory won’t make it run faster.&lt;/p&gt;
&lt;p&gt;For example, increasing the memory to 3008 MB (the maximum &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/limits.html&quot;&gt;memory limit&lt;/a&gt; at the time of this writing) will result in a similar runtime duration:&lt;/p&gt;
&lt;h5 id=&quot;memory-2048-mb&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#memory-2048-mb&quot;&gt;Memory 2048 MB&lt;/a&gt;&lt;/h5&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Test run&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Duration&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Billed Duration&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Cold Start Duration&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3775,63 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3800 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;392,59 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3604,71 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3700 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3682,62 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3700 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3677,14 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3700 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3725,77 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3800 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h5 id=&quot;memory-3008-mb&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#memory-3008-mb&quot;&gt;Memory 3008 MB&lt;/a&gt;&lt;/h5&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Test run&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Duration&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Billed Duration&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Cold Start Duration&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;4125,12 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;4200 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;407,92 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3767,79 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3800 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3736,06 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3800 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3662,68 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3700 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3717,01 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3800 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;When done optimizing, make sure to apply a sensible value for the Lambda timeout.
In this case, 6 seconds covered these test runs, though production needs headroom for slower inputs and cold starts.&lt;/p&gt;
&lt;h2 id=&quot;comparing-costs&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#comparing-costs&quot;&gt;Comparing costs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To compare costs between both implementations, I did a couple of test runs converting a 3-minute (2.8 MB) WebM audio file to MP3.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Warning&lt;/p&gt;
&lt;p&gt;The following comparison uses the 2019 rates and measured billed durations (including the 100 ms billing increments used then).
It is by no means very extensive, and your mileage may vary.
The free-tier calculations assume the monthly allowances are available to this workload.
S3 storage and request costs are excluded.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;amazon-elastic-transcoder-costs&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#amazon-elastic-transcoder-costs&quot;&gt;Amazon Elastic Transcoder costs&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Elastic Transcoder &lt;a href=&quot;https://aws.amazon.com/blogs/aws/audio-support-for-the-amazon-elastic-transcoder/&quot;&gt;charged per minute of output audio&lt;/a&gt; (rounded up to whole minutes) with 20 free audio minutes per month.
The rate used for these calculations in &lt;code&gt;eu-west-1&lt;/code&gt; was &lt;code&gt;$0,00522&lt;/code&gt; per output minute.
Processing time affects how soon the file is ready, but is not the billing unit.&lt;/p&gt;
&lt;p&gt;These are the timing results of the test runs:&lt;/p&gt;

















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Test run&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Transcoding Time&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;7638 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;6663 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;7729 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;6595 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;8752 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;7216 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;7&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;7167 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;8&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;6605 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;9&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;6718 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;8700 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;So the average transcoding time of the audio file would be:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;7638 + 6663 + 7729 + 6595 + 8752 + 7216 + 7167 + 6605 + 6718 + 8700 = 73 783 ms
73783 / 10 = 7378,3 ms
7378,3 / 1000 = 7,3783 sec&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Let’s say we would be transcoding &lt;code&gt;100 000&lt;/code&gt; of these audio files per month, with each output exactly 3 minutes long.
That would amount to:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;3 * 100 000 = 300 000 output minutes&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Since we pay &lt;code&gt;$0,00522&lt;/code&gt; per minute, the costs without free tier would be:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;300 000 * 0,00522 = $1 566&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;And with free tier it would cost:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;(300 000 - 20) * 0,00522 = $1 565,8956&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h4 id=&quot;what-about-lambda-costs&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#what-about-lambda-costs&quot;&gt;What about Lambda costs?&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;We’re using Lambda to schedule Amazon Elastic Transcoder jobs.
So we also have to calculate those (minor if not negligible) costs.&lt;/p&gt;
&lt;p&gt;The Lambda &lt;a href=&quot;https://aws.amazon.com/lambda/pricing&quot;&gt;pricing&lt;/a&gt; page tells us we pay for the &lt;strong&gt;number of requests&lt;/strong&gt; and the &lt;strong&gt;duration&lt;/strong&gt; (which depends on memory setting).&lt;/p&gt;
&lt;p&gt;The request rate was &lt;code&gt;$0,20&lt;/code&gt; per million requests, with 1 million requests free each month.
For &lt;code&gt;100 000&lt;/code&gt; invocations, that’s &lt;code&gt;$0,02&lt;/code&gt; without free tier, or zero with an unused allowance.
We’ll calculate duration costs below and include the request charge in the final totals.&lt;/p&gt;
&lt;p&gt;These are the Lambda durations (with 128 MB memory) for the accompanying transcoder test runs:&lt;/p&gt;







































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Test run&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Duration&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Billed Duration&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Cold Start Duration&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;494,08 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;500 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;401,61 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;185,01 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;200 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;168,29 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;200 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;165,29 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;200 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;184,89 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;200 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;210,19 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;300 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;7&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;162,64 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;200 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;8&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;178,79 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;200 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;9&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;318,84 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;400 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;206,18 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;300 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The average billed duration would be:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;500 + 200 + 200 + 200 + 200 + 300 + 200 + 200 + 400 + 300 = 2700 ms
2700 / 10 = 270 ms
270 / 1000 = 0,27 sec&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;In &lt;code&gt;eu-west-1&lt;/code&gt; we pay &lt;code&gt;$0,000 016 6667&lt;/code&gt; for every GB-second (GB-s).
That means we first have to calculate “how much” memory the Lambda function uses for its runtime duration.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;100 000&lt;/code&gt; transcoding jobs per month (with 128 MB memory) that would be:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;100 000 * 0,27 = 27000 sec
(128 / 1024) * 27000 = 3375 GB-s&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The free tier included &lt;code&gt;400 000&lt;/code&gt; GB-s every month, so depending on your scale you may or may not have to include it in your calculations.
But without free tier it would cost:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;3375 * 0,000 016 6667 = $0,056 250 113&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;ffmpeg-and-lambda-layers-costs&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#ffmpeg-and-lambda-layers-costs&quot;&gt;FFmpeg and Lambda Layers costs&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;These are the Lambda durations (with 2048 MB memory) of the test runs:&lt;/p&gt;







































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Test run&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Duration&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Billed Duration&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Cold Start Duration&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;1&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;4068,56 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;4100 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;408,17 ms&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3880,55 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3900 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;3&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3910,52 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;4000 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;4&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3794,20 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3800 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;5&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3856,73 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3900 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;6&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3859,06 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3900 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;7&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3810,93 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3900 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;8&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3799,19 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3800 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;9&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3858,49 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3900 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;10&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3866,53 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;3900 ms&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The average &lt;em&gt;billed duration&lt;/em&gt; would be:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;4100 + 3900 + 4000 + 3800 + 3900 + 3900 + 3900 + 3800 + 3900 + 3900 = 39100 ms
39100 / 10 = 3910 ms
3910 / 1000 = 3,91 sec&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;In &lt;code&gt;eu-west-1&lt;/code&gt; we pay &lt;code&gt;$0,000 016 6667&lt;/code&gt; for every GB-s.
For &lt;code&gt;100 000&lt;/code&gt; transcoding jobs (with 2048 MB memory) that would be:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;100 000 * 3,91 = 391 000 sec
(2048 / 1024) * 391 000 = 782 000 GB-s&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Without free tier it would cost:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;782 000 * 0,000 016 6667 = $13,033 3594&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;With free tier it would cost:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;(782 000 - 400 000) * 0,000 016 6667 = $6,366 6794&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;what-about-data-transfer-costs&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#what-about-data-transfer-costs&quot;&gt;What about data transfer costs?&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote cite=&quot;https://aws.amazon.com/lambda/pricing&quot;&gt;
&lt;p&gt;Data transferred between S3, Glacier, DynamoDB, SES, SQS, Kinesis, ECR, SNS, or SimpleDB and Lambda functions &lt;strong&gt;in the same AWS Region is free&lt;/strong&gt;.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://aws.amazon.com/lambda/pricing&quot;&gt;https://aws.amazon.com/lambda/pricing&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;p&gt;Otherwise data transferred into and out of Lambda functions will be charged at the &lt;a href=&quot;https://aws.amazon.com/ec2/pricing/on-demand&quot;&gt;EC2 data transfer rates&lt;/a&gt; as listed under the “Data transfer” section.&lt;/p&gt;
&lt;h3 id=&quot;putting-it-all-together&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#putting-it-all-together&quot;&gt;Putting it all together&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Costs of transcoding &lt;code&gt;100 000&lt;/code&gt; 3-minute (2.8 MB) WebM audio files to MP3 per month:&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Implementation&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Cost without free tier&lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Cost with free tier&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;Amazon Elastic Transcoder&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$1 566,08&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$1 565,90&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;FFmpeg and Lambda Layers&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$13,05&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;$6,37&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 id=&quot;footnote-label&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/audio-transcoding#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;In Safari the Media Recording API is hidden behind a feature flag, but not all events are supported. &lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;A container is not always required. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API&quot;&gt;WebRTC&lt;/a&gt; does not use a container at all. Instead, it streams the encoded audio and video tracks directly from one peer to another using &lt;code&gt;MediaStreamTrack&lt;/code&gt; objects to represent each track. &lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;If you’d like to learn more about audio codecs, I recommend reading the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Audio_codecs&quot;&gt;Mozilla web audio codec guide&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-4&quot;&gt;
&lt;p&gt;At the time of this writing a Lambda function can use &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html&quot;&gt;up to 5 layers at a time&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fnref-4&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-5&quot;&gt;
&lt;p&gt;These FFmpeg builds are all compatible with Amazon Linux 2. This is the operating system on which Lambda runs when the &lt;code&gt;Node.js&lt;/code&gt; &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html&quot;&gt;runtime&lt;/a&gt; is used. &lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fnref-5&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 5&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-6&quot;&gt;
&lt;p&gt;At the time of this writing the &lt;code&gt;/tmp&lt;/code&gt; directory allows you to &lt;em&gt;temporarily&lt;/em&gt; store up to &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/limits.html&quot;&gt;512 MB&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fnref-6&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 6&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-7&quot;&gt;
&lt;p&gt;At the time of this writing the maximum timeout is &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/limits.html&quot;&gt;900 seconds&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/lambda/audio-transcoding#user-content-fnref-7&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 7&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>Serverless auth</title><link>https://dans.land/garden/lambda/serverless-auth</link><guid isPermaLink="true">https://dans.land/garden/lambda/serverless-auth</guid><description>Protecting AWS API Gateway endpoints with AWS Lambda and Auth0.</description><pubDate>Wed, 19 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Historical walkthrough&lt;/p&gt;
&lt;p&gt;This post uses Node.js 8.10, Serverless 1 and the library versions available in 2019.
The runtime is &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html&quot;&gt;no longer supported by Lambda&lt;/a&gt;,
and the configuration and dependencies need updating when following this as a deployment guide.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Auth is complicated.
It can be difficult to reason about and can be hard to work with.
The terminology can be complex as well, and terms are sometimes used interchangeably or can be ambiguous.
Like saying “auth” to refer both to authentication (who are you?) and authorization (I know who you are, but what are you allowed to do?).&lt;/p&gt;
&lt;p&gt;On top of that it can also be challenging to know when to use what.
Depending on what you’re building and for whom, different auth protocols and strategies might be more suitable or required.&lt;/p&gt;
&lt;p&gt;This page does not explore these protocols and strategies in depth.
Instead, I want to show that implementing something as complex as auth doesn’t have to be too difficult.
In order to do that I’ll focus on a specific (but common) use case, and show a way to implement it.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Just want to read the code?&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/danillouz/serverless-auth&quot;&gt;github.com/danillouz/serverless-auth&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;use-case-and-technologies&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#use-case-and-technologies&quot;&gt;Use case and technologies&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;How can we secure an HTTP API with a token-based authentication strategy, so only authenticated and authorized clients can access it?&lt;/p&gt;
&lt;p&gt;More specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The HTTP API is an &lt;a href=&quot;https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html&quot;&gt;AWS API Gateway&lt;/a&gt; (APIG).&lt;/li&gt;
&lt;li&gt;The API endpoints are protected with a &lt;a href=&quot;https://oauth.net/2/bearer-tokens&quot;&gt;bearer token&lt;/a&gt; and implemented as &lt;a href=&quot;https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html&quot;&gt;Lambda Proxy Integrations&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://auth0.com/&quot;&gt;Auth0&lt;/a&gt; is used as a third-party auth provider.&lt;/li&gt;
&lt;li&gt;An &lt;a href=&quot;https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html&quot;&gt;APIG Lambda Authorizer&lt;/a&gt; is used to verify the token with Auth0.&lt;/li&gt;
&lt;li&gt;The Lambdas are implemented using &lt;a href=&quot;https://nodejs.org/en&quot;&gt;Node.js&lt;/a&gt; and the &lt;a href=&quot;https://serverless.com/&quot;&gt;Serverless Framework&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/CURL&quot;&gt;curl&lt;/a&gt; is used as a “client” to send HTTP requests to the API with a token.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-use-a-third-party-auth-provider&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#why-use-a-third-party-auth-provider&quot;&gt;Why use a third-party auth provider?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’ll be using Auth0 as a third-party auth provider.
This means that I’m choosing &lt;em&gt;not&lt;/em&gt; to build (nor operate) my own “auth server”.&lt;/p&gt;
&lt;p&gt;You can build an auth server yourself, for example using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://oauth.net/2&quot;&gt;OAuth 2.0&lt;/a&gt;: an authorization protocol.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openid.net/connect&quot;&gt;OpenID Connect&lt;/a&gt; (OIDC): an authentication protocol. This is an “identity layer” built on top of OAuth 2.0.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://auth0.com/learn/token-based-authentication-made-easy&quot;&gt;Token-based authentication&lt;/a&gt;: a strategy that requires a client to send a signed bearer token when making requests to a protected API. The API will only respond to requests successfully when it receives a verified token.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7519&quot;&gt;JSON Web Token&lt;/a&gt; (JWT): a way to send auth information (i.e. “claims”) as JSON. A signed JWT contains a &lt;code&gt;Header&lt;/code&gt;, &lt;code&gt;Payload&lt;/code&gt; and &lt;code&gt;Signature&lt;/code&gt; which are base64url encoded and separated by a period. In effect, a JWT can be used as a bearer token&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/serverless-auth#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But it will cost (a lot of) time, energy and money to build, operate and maintain it.&lt;/p&gt;
&lt;p&gt;There are valid use cases for rolling your own though.
However, using a third-party auth provider can increase shipping velocity.&lt;/p&gt;
&lt;h2 id=&quot;what-will-we-build&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#what-will-we-build&quot;&gt;What will we build?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’ll build an Account API with a single endpoint that returns some profile information for a user.&lt;/p&gt;
&lt;p&gt;Requirements and constraints are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The endpoint will be &lt;code&gt;GET /profile&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The business logic of the endpoint will be implemented by a Lambda handler:
&lt;ul&gt;
&lt;li&gt;The Lambda will return data as JSON.&lt;/li&gt;
&lt;li&gt;The Lambda will return a single property &lt;code&gt;name&lt;/code&gt; with value &lt;code&gt;Daniël&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The Lambda will return HTTP status code &lt;code&gt;200&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The endpoint will require a bearer token to return the profile data.
&lt;ul&gt;
&lt;li&gt;The token will be sent via the &lt;code&gt;Authorization&lt;/code&gt; request header.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Authorization&lt;/code&gt; request header value must have the format: &lt;code&gt;Bearer &amp;lt;TOKEN&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The token is verified by a Lambda Authorizer with the help of Auth0.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This API isn’t very useful, but gives us something to work with in order to implement auth.&lt;/p&gt;
&lt;h3 id=&quot;example&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#example&quot;&gt;Example&lt;/a&gt;&lt;/h3&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP request&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;GET /profile
Authorization: Bearer eyJ...lKw&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP response&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;200 OK
Content-Type: application/json

{
  &quot;name&quot;: &quot;Daniël&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;registering-the-api-with-auth0&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#registering-the-api-with-auth0&quot;&gt;Registering the API with Auth0&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When the Account API receives a request with the bearer token, it will have to verify the token with the help of Auth0. In order to do that, we first have to register our API with them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://auth0.com/signup&quot;&gt;Sign up&lt;/a&gt; and set up your tenant.&lt;/li&gt;
&lt;li&gt;In the Auth0 dashboard, navigate to “APIs” and click on “Create API”.&lt;/li&gt;
&lt;li&gt;Follow the &lt;a href=&quot;https://auth0.com/docs/get-started/apis&quot;&gt;instructions&lt;/a&gt; and provide a “Name” and “Identifier”. For example &lt;code&gt;Account API&lt;/code&gt; and &lt;code&gt;https://api.danillouz.dev/account&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/serverless-auth#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;RS256&lt;/code&gt; as the signing algorithm (more on that later).&lt;/li&gt;
&lt;li&gt;Click on “Create”.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Auth0 API creation form&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2728&quot; height=&quot;1836&quot; src=&quot;https://dans.land/assets/register.C_AKYMZY_Z2AG2e.webp&quot; srcset=&quot;https://dans.land/assets/register.C_AKYMZY_ZfXOlF.webp 320w, https://dans.land/assets/register.C_AKYMZY_3wXvu.webp 480w, https://dans.land/assets/register.C_AKYMZY_wwzBt.webp 672w, https://dans.land/assets/register.C_AKYMZY_1dnFl3.webp 960w, https://dans.land/assets/register.C_AKYMZY_1Kq9F8.webp 1344w, https://dans.land/assets/register.C_AKYMZY_Z2AG2e.webp 2728w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Auth0 API creation form&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h3 id=&quot;lambda-authorizer-configuration&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#lambda-authorizer-configuration&quot;&gt;Lambda Authorizer configuration&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that our API is registered, we need to take note of the following (public) properties, to later on configure our Lambda Authorizer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Token issuer: use the exact issuer configured for your Auth0 tenant, including its trailing slash. For example &lt;code&gt;https://danillouz.eu.auth0.com/&lt;/code&gt;. Tenant and custom domains can have different formats.&lt;/li&gt;
&lt;li&gt;JWKS URI: this returns a &lt;a href=&quot;https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets&quot;&gt;JSON Web Key Set&lt;/a&gt; (JWKS). The URI will be used by the Lambda Authorizer to fetch a public key from Auth0 and verify a token (more on that later). Use the JWKS URI for your configured issuer. For example &lt;code&gt;https://danillouz.eu.auth0.com/.well-known/jwks.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Audience: this is the “Identifier” you provided during step 3 of &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#registering-the-api-with-auth0&quot;&gt;Registering the API with Auth0&lt;/a&gt;. For example &lt;code&gt;https://api.danillouz.dev/account&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also find these values under the “Quick Start” tab of the API details screen (you were redirected there after registering the API). For example, click on the “Node.js” tab and look for these properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;issuer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jwksUri&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;audience&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Auth0 API Quick Start values&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2144&quot; height=&quot;1840&quot; src=&quot;https://dans.land/assets/quick-start.D9Z3f11__1kNDq8.webp&quot; srcset=&quot;https://dans.land/assets/quick-start.D9Z3f11__Z1c7IX7.webp 320w, https://dans.land/assets/quick-start.D9Z3f11__1x0ROj.webp 480w, https://dans.land/assets/quick-start.D9Z3f11__ZuUnfb.webp 672w, https://dans.land/assets/quick-start.D9Z3f11__Z18TdrW.webp 960w, https://dans.land/assets/quick-start.D9Z3f11__Za9Ixj.webp 1344w, https://dans.land/assets/quick-start.D9Z3f11__1kNDq8.webp 2144w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Auth0 API Quick Start values&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;whats-a-lambda-authorizer&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#whats-a-lambda-authorizer&quot;&gt;What’s a Lambda Authorizer?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I haven’t explained what a Lambda Authorizer is yet.
In short, it’s a feature of APIG to control access to an API.&lt;/p&gt;
&lt;blockquote cite=&quot;https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html&quot;&gt;
&lt;p&gt;A Lambda authorizer is useful if you want to implement a custom authorization scheme that uses a bearer token authentication strategy such as OAuth.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html&quot;&gt;https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;p&gt;There are actually two types of Lambda Authorizers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Token-based authorizers.&lt;/li&gt;
&lt;li&gt;Request parameter based authorizers.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We’ll be using the token-based authorizer, because that supports bearer tokens.&lt;/p&gt;
&lt;h3 id=&quot;what-should-it-do&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#what-should-it-do&quot;&gt;What should it do?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When a Lambda Authorizer is configured, and a client makes a request to APIG, AWS will invoke the Lambda Authorizer &lt;em&gt;first&lt;/em&gt; (i.e. before the Lambda handler).
The Lambda Authorizer must then extract the bearer token from the &lt;code&gt;Authorization&lt;/code&gt; request header and validate it by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetching the JWKS (which contains the public key) from Auth0 using the JWKS URI&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/serverless-auth#user-content-fn-3&quot; id=&quot;user-content-fnref-3&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;Verifying the token signature with the fetched public key.&lt;/li&gt;
&lt;li&gt;Verifying the token has the correct issuer and audience claims.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Only when the token passes these checks should the Lambda Authorizer return an &lt;a href=&quot;https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html&quot;&gt;IAM Policy&lt;/a&gt; document with &lt;code&gt;&quot;Effect&quot;&lt;/code&gt; set to &lt;code&gt;&quot;Allow&quot;&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Action&quot;: &quot;execute-api:Invoke&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Resource&quot;: &quot;ARN_OF_API_GATEWAY_METHOD&quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;It’s this policy that tells APIG it’s &lt;em&gt;allowed&lt;/em&gt; to invoke our downstream Lambda handler. In our case that will be the Lambda handler that returns the profile data.&lt;/p&gt;
&lt;p&gt;Alternatively, the Lambda authorizer may &lt;em&gt;deny&lt;/em&gt; invoking the downstream handler by setting &lt;code&gt;&quot;Effect&quot;&lt;/code&gt; to &lt;code&gt;&quot;Deny&quot;&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Action&quot;: &quot;execute-api:Invoke&quot;,
      &quot;Effect&quot;: &quot;Deny&quot;,
      &quot;Resource&quot;: &quot;ARN_OF_API_GATEWAY_METHOD&quot;
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This will make APIG respond with &lt;code&gt;403 Forbidden&lt;/code&gt;.
To make APIG respond with &lt;code&gt;401 Unauthorized&lt;/code&gt;, return an &lt;code&gt;Unauthorized&lt;/code&gt; error from the Lambda Authorizer.
We’ll see this in action when implementing the Lambda Authorizer.&lt;/p&gt;
&lt;h3 id=&quot;a-note-on-authorization&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#a-note-on-authorization&quot;&gt;A note on authorization&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I found it good practice to only &lt;em&gt;authenticate&lt;/em&gt; the caller from the Lambda Authorizer and apply &lt;em&gt;authorization&lt;/em&gt; logic downstream (i.e. in the Lambda handlers).&lt;/p&gt;
&lt;p&gt;This may not be feasible in all use cases, but doing this keeps the Lambda Authorizer &lt;em&gt;simple&lt;/em&gt;.
So I think that ideally the Lambda Authorizer is only responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Verifying the token.&lt;/li&gt;
&lt;li&gt;Propagating authorization information downstream.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The downstream Lambda handler can then use the authorization information to decide if it should execute its business logic for the specific caller or not.&lt;/p&gt;
&lt;p&gt;Following this design also leads to a nice decoupling between the authentication and authorization logic (i.e. between the Lambda Authorizer and Lambda handlers).&lt;/p&gt;
&lt;h4 id=&quot;scopes&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#scopes&quot;&gt;Scopes&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When using OAuth 2.0, scopes can be used to apply authorization logic.
In our case we could have a &lt;code&gt;get:profile&lt;/code&gt; scope.
A Lambda handler can check if the caller has been authorized to perform the action that is represented by the scope.
If the scope is not present, the Lambda handler can return a &lt;code&gt;403 Forbidden&lt;/code&gt; response to the caller.&lt;/p&gt;
&lt;p&gt;You can configure scope in the Auth0 dashboard by adding permissions to the registered API.
Navigate to the “Permissions” tab of the API details screen and add &lt;code&gt;get:profile&lt;/code&gt; as a scope.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Auth0 API permission for get:profile&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2158&quot; height=&quot;1842&quot; src=&quot;https://dans.land/assets/api-permissions.CuTiEdRS_Z1Mc8Qt.webp&quot; srcset=&quot;https://dans.land/assets/api-permissions.CuTiEdRS_18yob4.webp 320w, https://dans.land/assets/api-permissions.CuTiEdRS_1eqFVd.webp 480w, https://dans.land/assets/api-permissions.CuTiEdRS_fazHH.webp 672w, https://dans.land/assets/api-permissions.CuTiEdRS_ZezmIn.webp 960w, https://dans.land/assets/api-permissions.CuTiEdRS_YEOnX.webp 1344w, https://dans.land/assets/api-permissions.CuTiEdRS_Z1Mc8Qt.webp 2158w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Auth0 API permission for get:profile&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We’ll use this scope when implementing the Account API.
You can read more about scopes in the Auth0 &lt;a href=&quot;https://auth0.com/docs/get-started/apis/scopes&quot;&gt;docs&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;context&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#context&quot;&gt;Context&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You can propagate authorization information (like scopes) downstream by returning a &lt;code&gt;context&lt;/code&gt; object in the Lambda Authorizer’s response:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

module.exports.authorizer = (event) =&amp;gt; {
  const authResponse = {
    principalId: &quot;UNIQUE_ID&quot;,
    policyDocument: {
      Version: &quot;2012-10-17&quot;,
      Statement: [
        {
          Action: &quot;execute-api:Invoke&quot;,
          Effect: &quot;Allow&quot;,
          Resource: event.methodArn,
        },
      ],
    },
    context: {
      scope: &quot;get:profile&quot;,
    },
  }

  return authResponse
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;But there’s a caveat here. You can &lt;em&gt;not&lt;/em&gt; set a JSON serializable object or array as a valid value of any key in the &lt;code&gt;context&lt;/code&gt; object.
It can only be a &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Number&lt;/code&gt; or &lt;code&gt;Boolean&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;context: {
  a: &apos;value&apos;, // OK
  b: 1, // OK
  c: true, // OK
  d: [9, 8, 7], // Will NOT be serialized
  e: { x: &apos;value&apos;, y: 99, z: false } // Will NOT be serialized
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Any “valid” properties passed to the &lt;code&gt;context&lt;/code&gt; object will be made available to downstream Lambda handlers via the &lt;code&gt;event&lt;/code&gt; object:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

module.exports.handler = (event) =&amp;gt; {
  const { authorizer } = event.requestContext
  console.log(authorizer.scope) // &quot;get:profile&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2 id=&quot;solidifying-our-mental-model&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#solidifying-our-mental-model&quot;&gt;Solidifying our mental model&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With that covered, we’re ready to build the Lambda Authorizer and the Account API.
But before we do, let’s take a step back and solidify our mental model first.&lt;/p&gt;
&lt;p&gt;To summarize, we need the following components to protect our API:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Auth0 as the third-party auth provider to issue and help verify bearer tokens.&lt;/li&gt;
&lt;li&gt;APIG to represent the Account API.&lt;/li&gt;
&lt;li&gt;A Lambda Authorizer to verify tokens with Auth0.&lt;/li&gt;
&lt;li&gt;A Lambda handler for the &lt;code&gt;GET /profile&lt;/code&gt; endpoint to return the profile data.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl&lt;/code&gt; as the client to send HTTP requests to the API with a token.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can visualize how these components will interact with each other like this:&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Auth0 request authorization flow&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2048&quot; height=&quot;1536&quot; src=&quot;https://dans.land/assets/auth-flow.begSx5qE_1yRlSk.webp&quot; srcset=&quot;https://dans.land/assets/auth-flow.begSx5qE_rVteM.webp 320w, https://dans.land/assets/auth-flow.begSx5qE_ZUccCz.webp 480w, https://dans.land/assets/auth-flow.begSx5qE_2kulG5.webp 672w, https://dans.land/assets/auth-flow.begSx5qE_Z1BphuQ.webp 960w, https://dans.land/assets/auth-flow.begSx5qE_qm5pt.webp 1344w, https://dans.land/assets/auth-flow.begSx5qE_1yRlSk.webp 2048w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Auth0 request authorization flow&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; will send an HTTP request to the &lt;code&gt;GET /profile&lt;/code&gt; endpoint with a token via the &lt;code&gt;Authorization&lt;/code&gt; request header.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the HTTP request reaches APIG, it will check if a Lambda Authorizer is configured for the called endpoint. If so, APIG will invoke the Lambda Authorizer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Lambda Authorizer will then:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extract the token from the &lt;code&gt;Authorization&lt;/code&gt; request header.&lt;/li&gt;
&lt;li&gt;Fetch the JWKS (which contains the public key) from Auth0.&lt;/li&gt;
&lt;li&gt;Verify the token signature with the fetched public key.&lt;/li&gt;
&lt;li&gt;Verify the token has the correct issuer and audience claims.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the token is verified, the Lambda Authorizer will return an IAM Policy document with &lt;code&gt;Effect&lt;/code&gt; set to &lt;code&gt;Allow&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;APIG will now evaluate the IAM Policy and if the &lt;code&gt;Effect&lt;/code&gt; is set to &lt;code&gt;Allow&lt;/code&gt;, it will invoke the specified Lambda handler.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Lambda handler will execute and when the &lt;code&gt;get:profile&lt;/code&gt; scope is present, it will return the profile data to the client.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now for the easy part, writing the code!&lt;/p&gt;
&lt;h2 id=&quot;implementing-the-lambda-authorizer&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#implementing-the-lambda-authorizer&quot;&gt;Implementing the Lambda Authorizer&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’ll do this by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#1-setting-up-the-project&quot;&gt;1. Setting up the project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#2-configuring-a-serverless-manifest&quot;&gt;2. Configuring a Serverless manifest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#3-defining-the-lambda-authorizer&quot;&gt;3. Defining the Lambda Authorizer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#4-getting-the-token&quot;&gt;4. Getting the token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#5-verifying-the-token&quot;&gt;5. Verifying the token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#6-creating-the-auth-response&quot;&gt;6. Creating the auth response&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#7-releasing-the-lambda-authorizer&quot;&gt;7. Releasing the Lambda Authorizer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-setting-up-the-project&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#1-setting-up-the-project&quot;&gt;1. Setting up the project&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Create a new directory for the code:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;mkdir lambda-authorizers&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Move to this directory and initialize a new &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npm&lt;/a&gt; project with:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm init -y&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This creates a &lt;code&gt;package.json&lt;/code&gt; file.
Now you can install the following required npm dependencies:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm i jsonwebtoken jwks-rsa&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/auth0/node-jsonwebtoken&quot;&gt;jsonwebtoken&lt;/a&gt; library will help us decode the bearer token (a JWT) and verify its signature, issuer and audience claims.
The &lt;a href=&quot;https://github.com/auth0/node-jwks-rsa&quot;&gt;jwks-rsa&lt;/a&gt; library will help us fetch the JWKS from Auth0.&lt;/p&gt;
&lt;p&gt;We’ll use the Serverless Framework to configure and upload the Lambda to AWS, so install it as a dev dependency:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm i -D serverless&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;2-configuring-a-serverless-manifest&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#2-configuring-a-serverless-manifest&quot;&gt;2. Configuring a Serverless manifest&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Create a Serverless manifest:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/serverless.yaml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: lambda-authorizers

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage, &apos;prod&apos;}
  region: ${opt:region, &apos;eu-central-1&apos;}
  memorySize: 128
  timeout: 3

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Add the properties we got from the &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#lambda-authorizer-configuration&quot;&gt;Lambda Authorizer configuration&lt;/a&gt; as environment variables.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/serverless.yaml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: lambda-authorizers

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage, &apos;prod&apos;}
  region: ${opt:region, &apos;eu-central-1&apos;}
  memorySize: 128
  timeout: 3
  environment:
    JWKS_URI: &quot;https://danillouz.eu.auth0.com/.well-known/jwks.json&quot;
    TOKEN_ISSUER: &quot;https://danillouz.eu.auth0.com/&quot;
    AUDIENCE: &quot;https://api.danillouz.dev/account&quot;

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Add the Lambda function definition:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/serverless.yaml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: lambda-authorizers

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage, &apos;prod&apos;}
  region: ${opt:region, &apos;eu-central-1&apos;}
  memorySize: 128
  timeout: 3
  environment:
    JWKS_URI: &quot;https://danillouz.eu.auth0.com/.well-known/jwks.json&quot;
    TOKEN_ISSUER: &quot;https://danillouz.eu.auth0.com/&quot;
    AUDIENCE: &quot;https://api.danillouz.dev/account&quot;

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src

functions:
  auth0VerifyBearer:
    handler: src/auth0.verifyBearer
    description: Verifies the bearer token with the help of Auth0&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;3-defining-the-lambda-authorizer&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#3-defining-the-lambda-authorizer&quot;&gt;3. Defining the Lambda Authorizer&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In order to match the Lambda function definition in the Serverless manifest, create a file named &lt;code&gt;auth0.js&lt;/code&gt; in &lt;code&gt;src&lt;/code&gt;.
In that file export a method named &lt;code&gt;verifyBearer&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/src/auth0.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

module.exports.verifyBearer = async () =&amp;gt; {
  try {
    // Lambda Authorizer implementation goes here.
  } catch (err) {
    console.log(&quot;Authorizer Error: &quot;, err)
    throw new Error(&quot;Unauthorized&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;If something goes wrong in the Lambda, we’ll log the error and throw a new &lt;code&gt;Unauthorized&lt;/code&gt; error.
This will make APIG return a &lt;code&gt;401 Unauthorized&lt;/code&gt; response to the caller&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/serverless-auth#user-content-fn-4&quot; id=&quot;user-content-fnref-4&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&quot;4-getting-the-token&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#4-getting-the-token&quot;&gt;4. Getting the token&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Lambda will first have to get the bearer token from the &lt;code&gt;Authorization&lt;/code&gt; request header.
Create a helper function for that in &lt;code&gt;src/get-token.js&lt;/code&gt; and export a function named &lt;code&gt;getToken&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/src/get-token.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

module.exports = function getToken(event) {
  if (event.type !== &quot;TOKEN&quot;) {
    throw new Error(&apos;Authorizer must be of type &quot;TOKEN&quot;&apos;)
  }

  const { authorizationToken: bearer } = event
  if (!bearer) {
    throw new Error(&apos;Authorization header with &quot;Bearer TOKEN&quot; must be provided&apos;)
  }

  const [, token] = bearer.match(/^Bearer (.*)$/) || []
  if (!token) {
    throw new Error(&quot;Invalid bearer token&quot;)
  }

  return token
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Here we’re only interested in &lt;code&gt;TOKEN&lt;/code&gt; events because we’re implementing a &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#whats-a-lambda-authorizer&quot;&gt;token-based authorizer&lt;/a&gt;.
We can access the value of the &lt;code&gt;Authorization&lt;/code&gt; request header via the &lt;code&gt;event.authorizationToken&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;Then &lt;code&gt;require&lt;/code&gt; and call the helper in the Lambda with the APIG Lambda authorizer input &lt;a href=&quot;https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-input.html&quot;&gt;event&lt;/a&gt; as an argument:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/src/auth0.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const getToken = require(&quot;./get-token&quot;)

module.exports.verifyBearer = async (event) =&amp;gt; {
  try {
    const token = getToken(event)
  } catch (err) {
    console.log(&quot;Authorizer Error: &quot;, err)
    throw new Error(&quot;Unauthorized&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;5-verifying-the-token&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#5-verifying-the-token&quot;&gt;5. Verifying the token&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now we have the token, we need to verify it by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Decoding the bearer token (JWT).&lt;/li&gt;
&lt;li&gt;Fetching the public key from Auth0 using the JWKS URI (used to verify the token signature).&lt;/li&gt;
&lt;li&gt;Verifying the token signature, issuer and audience claims.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We’ll use another helper function for this.
Create one in &lt;code&gt;src/verify-token.js&lt;/code&gt; and export a function named &lt;code&gt;verifyToken&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/src/verify-token.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

module.exports = async function verifyToken(
  token,
  decodeJwt,
  getSigningKey,
  verifyJwt,
  issuer,
  audience,
) {
  // Step 1.
  const decoded = decodeJwt(token, { complete: true })

  if (!decoded || !decoded.header || !decoded.header.kid) {
    throw new Error(&quot;Invalid JWT&quot;)
  }

  // Step 2.
  const { publicKey, rsaPublicKey } = await getSigningKey(decoded.header.kid)
  const signingKey = publicKey || rsaPublicKey

  // Step 3.
  return verifyJwt(token, signingKey, {
    algorithms: [&quot;RS256&quot;],
    issuer,
    audience,
  })
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;After we decode the token with the option &lt;code&gt;{ complete: true }&lt;/code&gt; we can access the JWT &lt;code&gt;header&lt;/code&gt; data.
By using the &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7517.html#section-4.5&quot;&gt;kid&lt;/a&gt; header parameter we can find out which key was used to sign the token.&lt;/p&gt;
&lt;p&gt;When we registered the API with Auth0 we chose the &lt;code&gt;RS256&lt;/code&gt; signing algorithm.
This algorithm generates an asymmetric signature.
This basically means that Auth0 uses a &lt;em&gt;private key&lt;/em&gt; to sign a JWT when it issues one.
We can use a &lt;em&gt;public key&lt;/em&gt; (fetched via the JWKS URI) to verify the authenticity of the token.&lt;/p&gt;
&lt;p&gt;First require the helper in the Lambda and pass the &lt;code&gt;token&lt;/code&gt; as the first argument when calling it:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/src/auth0.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const getToken = require(&quot;./get-token&quot;)
const verifyToken = require(&quot;./verify-token&quot;)

module.exports.verifyBearer = async (event) =&amp;gt; {
  try {
    const token = getToken(event)
    const verifiedData = await verifyToken(token)
  } catch (err) {
    console.log(&quot;Authorizer Error: &quot;, err)
    throw new Error(&quot;Unauthorized&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To decode the token in the helper (step 1) we’ll use the &lt;code&gt;jsonwebtoken&lt;/code&gt; library.
It exposes a &lt;code&gt;decode&lt;/code&gt; method.
Pass this method as the second argument when calling the helper:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/src/auth0.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const jwt = require(&quot;jsonwebtoken&quot;)

const getToken = require(&quot;./get-token&quot;)
const verifyToken = require(&quot;./verify-token&quot;)

module.exports.verifyBearer = async (event) =&amp;gt; {
  try {
    const token = getToken(event)
    const verifiedData = await verifyToken(token, jwt.decode)
  } catch (err) {
    console.log(&quot;Authorizer Error: &quot;, err)
    throw new Error(&quot;Unauthorized&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To fetch the public key from Auth0 (step 2) we’ll use the &lt;code&gt;jwks-rsa&lt;/code&gt; library.
It exposes a client with &lt;code&gt;getSigningKey&lt;/code&gt; method to fetch the key.
Pass a “promisified” version of this method as the third argument when calling the helper:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/src/auth0.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const util = require(&quot;util&quot;)
const jwt = require(&quot;jsonwebtoken&quot;)
const jwksRSA = require(&quot;jwks-rsa&quot;)

const getToken = require(&quot;./get-token&quot;)
const verifyToken = require(&quot;./verify-token&quot;)

const { JWKS_URI } = process.env

const jwksClient = jwksRSA({
  cache: true,
  rateLimit: true,
  jwksUri: JWKS_URI,
})
const getSigningKey = util.promisify(jwksClient.getSigningKey)

module.exports.verifyBearer = async (event) =&amp;gt; {
  try {
    const token = getToken(event)
    const verifiedData = await verifyToken(token, jwt.decode, getSigningKey)
  } catch (err) {
    console.log(&quot;Authorizer Error: &quot;, err)
    throw new Error(&quot;Unauthorized&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Finally, to verify the token signature, issuer and audience claims (step 3) we’ll use the &lt;code&gt;jsonwebtoken&lt;/code&gt; library again.
It exposes a &lt;code&gt;verify&lt;/code&gt; method.
Pass a “promisified” version of this method together with the &lt;code&gt;TOKEN_ISSUER&lt;/code&gt; and &lt;code&gt;AUDIENCE&lt;/code&gt; as the final arguments when calling the helper:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/src/auth0.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const util = require(&quot;util&quot;)
const jwt = require(&quot;jsonwebtoken&quot;)
const jwksRSA = require(&quot;jwks-rsa&quot;)

const getToken = require(&quot;./get-token&quot;)
const verifyToken = require(&quot;./verify-token&quot;)

const { JWKS_URI, TOKEN_ISSUER, AUDIENCE } = process.env

const jwksClient = jwksRSA({
  cache: true,
  rateLimit: true,
  jwksUri: JWKS_URI,
})
const getSigningKey = util.promisify(jwksClient.getSigningKey)
const verifyJwt = util.promisify(jwt.verify)

module.exports.verifyBearer = async (event) =&amp;gt; {
  try {
    const token = getToken(event)
    const verifiedData = await verifyToken(
      token,
      jwt.decode,
      getSigningKey,
      verifyJwt,
      TOKEN_ISSUER,
      AUDIENCE,
    )
  } catch (err) {
    console.log(&quot;Authorizer Error: &quot;, err)
    throw new Error(&quot;Unauthorized&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;When the helper verifies the token, it will return the JWT payload data (with all claims) as &lt;code&gt;verifiedData&lt;/code&gt;.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;verifiedData&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;iss&quot;: &quot;https://danillouz.eu.auth0.com/&quot;,
  &quot;sub&quot;: &quot;FHgLVARPk8oXjsP5utP8wYAnZePPAkw1@clients&quot;,
  &quot;aud&quot;: &quot;https://api.danillouz.dev/account&quot;,
  &quot;iat&quot;: 1560762850,
  &quot;exp&quot;: 1560849250,
  &quot;azp&quot;: &quot;FHgLVARPk8oXjsP5utP8wYAnZePPAkw1&quot;,
  &quot;gty&quot;: &quot;client-credentials&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;6-creating-the-auth-response&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#6-creating-the-auth-response&quot;&gt;6. Creating the auth response&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We’ll use &lt;code&gt;verifiedData&lt;/code&gt; to create the &lt;code&gt;authResponse&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/src/auth0.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const util = require(&quot;util&quot;)
const jwt = require(&quot;jsonwebtoken&quot;)
const jwksRSA = require(&quot;jwks-rsa&quot;)

const getToken = require(&quot;./get-token&quot;)
const verifyToken = require(&quot;./verify-token&quot;)

const { JWKS_URI, TOKEN_ISSUER, AUDIENCE } = process.env

const jwksClient = jwksRSA({
  cache: true,
  rateLimit: true,
  jwksUri: JWKS_URI,
})
const getSigningKey = util.promisify(jwksClient.getSigningKey)
const verifyJwt = util.promisify(jwt.verify)

module.exports.verifyBearer = async (event) =&amp;gt; {
  try {
    const token = getToken(event)
    const verifiedData = await verifyToken(
      token,
      jwt.decode,
      getSigningKey,
      verifyJwt,
      TOKEN_ISSUER,
      AUDIENCE,
    )
    const authResponse = {
      principalId: verifiedData.sub,
      policyDocument: {
        Version: &quot;2012-10-17&quot;,
        Statement: [
          {
            Action: &quot;execute-api:Invoke&quot;,
            Effect: &quot;Allow&quot;,
            Resource: event.methodArn,
          },
        ],
      },
    }
    return authResponse
  } catch (err) {
    console.log(&quot;Authorizer Error: &quot;, err)
    throw new Error(&quot;Unauthorized&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h4 id=&quot;principal-identifier&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#principal-identifier&quot;&gt;Principal identifier&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;authResponse.principalId&lt;/code&gt; property must represent a unique (user) identifier associated with the token sent by the client.
Auth0 provides this via the &lt;code&gt;sub&lt;/code&gt; claim and ours has the value:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;verifiedData&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;iss&quot;: &quot;https://danillouz.eu.auth0.com/&quot;,
  &quot;sub&quot;: &quot;FHgLVARPk8oXjsP5utP8wYAnZePPAkw1@clients&quot;, // Principal ID
  &quot;aud&quot;: &quot;https://api.danillouz.dev/account&quot;,
  &quot;iat&quot;: 1560762850,
  &quot;exp&quot;: 1560849250,
  &quot;azp&quot;: &quot;FHgLVARPk8oXjsP5utP8wYAnZePPAkw1&quot;,
  &quot;gty&quot;: &quot;client-credentials&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Note that if you use an Auth0 test token (like we’ll do in a bit), the &lt;code&gt;sub&lt;/code&gt; claim will be postfixed with &lt;code&gt;@clients&lt;/code&gt;.
This is because Auth0 automatically created a “Test Application” for us when we registered the Account API with them.
It’s via this application that we obtain the test token, obtained via the &lt;a href=&quot;https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-credentials-flow&quot;&gt;client credentials grant&lt;/a&gt; (specified by the &lt;code&gt;gty&lt;/code&gt; claim).&lt;/p&gt;
&lt;p&gt;In this case the test application represents a “machine” and &lt;em&gt;not&lt;/em&gt; a user.
But that’s okay because the machine has a unique identifier the same way a user would have (by means of a client ID).
This means that this implementation will also work when using “user-centric” auth flows that issue an access token for the API, such as the &lt;a href=&quot;https://auth0.com/docs/authenticate/login/oidc-conformant-authentication/oidc-adoption-implicit-flow&quot;&gt;legacy implicit grant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can find the test application in the Auth0 dashboard by navigating to “Applications” and selecting “Account API (Test Application)”.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Auth0 test application&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2120&quot; height=&quot;1842&quot; src=&quot;https://dans.land/assets/test-application.xKgTUbZN_ZEMSi6.webp&quot; srcset=&quot;https://dans.land/assets/test-application.xKgTUbZN_1RS4bR.webp 320w, https://dans.land/assets/test-application.xKgTUbZN_Z37Azl.webp 480w, https://dans.land/assets/test-application.xKgTUbZN_Zkd7xp.webp 672w, https://dans.land/assets/test-application.xKgTUbZN_W1m6J.webp 960w, https://dans.land/assets/test-application.xKgTUbZN_1pCUmg.webp 1344w, https://dans.land/assets/test-application.xKgTUbZN_ZEMSi6.webp 2120w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Auth0 test application&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Warning&lt;/p&gt;
&lt;p&gt;For new browser clients, use the &lt;a href=&quot;https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce&quot;&gt;authorization code flow with PKCE&lt;/a&gt; instead of the implicit flow mentioned above.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;method-arn&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#method-arn&quot;&gt;Method ARN&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html&quot;&gt;ARN&lt;/a&gt; in &lt;code&gt;event.methodArn&lt;/code&gt; identifies the requested API Gateway method (&lt;code&gt;execute-api&lt;/code&gt;), not the downstream Lambda handler.
The policy uses this ARN to allow or deny access to that API method.
In our case, an allowed request reaches the Lambda handler that gets the profile data.&lt;/p&gt;
&lt;h4 id=&quot;granting-a-client-scopes&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#granting-a-client-scopes&quot;&gt;Granting a client scopes&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;As mentioned when discussing &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#scopes&quot;&gt;Scopes&lt;/a&gt;, Auth0 can provide scopes as authorization information.
In order for Auth0 to do this, we need to “grant” our client the &lt;code&gt;get:profile&lt;/code&gt; scope.
In our case, the client is the “Test Application” that has been created for us.&lt;/p&gt;
&lt;p&gt;Navigate to the “APIs” tab in the “Test Application” details and click on the “right pointing chevron” (circled in red) to the right of “Account API”.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Auth0 test application API access&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2118&quot; height=&quot;1834&quot; src=&quot;https://dans.land/assets/grant-scope-1.CntpElRj_ZPWHTE.webp&quot; srcset=&quot;https://dans.land/assets/grant-scope-1.CntpElRj_Z2cdigx.webp 320w, https://dans.land/assets/grant-scope-1.CntpElRj_ZHPcKG.webp 480w, https://dans.land/assets/grant-scope-1.CntpElRj_ZT9kzU.webp 672w, https://dans.land/assets/grant-scope-1.CntpElRj_1TUqS.webp 960w, https://dans.land/assets/grant-scope-1.CntpElRj_Z1IuGdn.webp 1344w, https://dans.land/assets/grant-scope-1.CntpElRj_ZPWHTE.webp 2118w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Auth0 test application API access&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Then check the &lt;code&gt;get:profile&lt;/code&gt; scope, click “Update” and click “Continue”.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Auth0 get:profile scope selection&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2102&quot; height=&quot;1842&quot; src=&quot;https://dans.land/assets/grant-scope-2.B-hHB_kK_2dGhEa.webp&quot; srcset=&quot;https://dans.land/assets/grant-scope-2.B-hHB_kK_1XVjSF.webp 320w, https://dans.land/assets/grant-scope-2.B-hHB_kK_ZkmWvo.webp 480w, https://dans.land/assets/grant-scope-2.B-hHB_kK_Z1Y1Qr.webp 672w, https://dans.land/assets/grant-scope-2.B-hHB_kK_15x1wK.webp 960w, https://dans.land/assets/grant-scope-2.B-hHB_kK_ZXf6jY.webp 1344w, https://dans.land/assets/grant-scope-2.B-hHB_kK_2dGhEa.webp 2102w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Auth0 get:profile scope selection&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Now the configured scope will be a claim on issued test tokens, and part of the &lt;code&gt;verifiedData&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;verifiedData&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;iss&quot;: &quot;https://danillouz.eu.auth0.com/&quot;,
  &quot;sub&quot;: &quot;FHgLVARPk8oXjsP5utP8wYAnZePPAkw1@clients&quot;,
  &quot;aud&quot;: &quot;https://api.danillouz.dev/account&quot;,
  &quot;iat&quot;: 1560762850,
  &quot;exp&quot;: 1560849250,
  &quot;azp&quot;: &quot;FHgLVARPk8oXjsP5utP8wYAnZePPAkw1&quot;,
  &quot;scope&quot;: &quot;get:profile&quot;, // Scope is now a claim
  &quot;gty&quot;: &quot;client-credentials&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;So we can propagate it to downstream Lambda handlers like this:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/src/auth0.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const util = require(&quot;util&quot;)
const jwt = require(&quot;jsonwebtoken&quot;)
const jwksRSA = require(&quot;jwks-rsa&quot;)

const getToken = require(&quot;./get-token&quot;)
const verifyToken = require(&quot;./verify-token&quot;)

const { JWKS_URI, TOKEN_ISSUER, AUDIENCE } = process.env

const jwksClient = jwksRSA({
  cache: true,
  rateLimit: true,
  jwksUri: JWKS_URI,
})
const getSigningKey = util.promisify(jwksClient.getSigningKey)
const verifyJwt = util.promisify(jwt.verify)

module.exports.verifyBearer = async (event) =&amp;gt; {
  try {
    const token = getToken(event)
    const verifiedData = await verifyToken(
      token,
      jwt.decode,
      getSigningKey,
      verifyJwt,
      TOKEN_ISSUER,
      AUDIENCE,
    )
    const authResponse = {
      principalId: verifiedData.sub,
      policyDocument: {
        Version: &quot;2012-10-17&quot;,
        Statement: [
          {
            Action: &quot;execute-api:Invoke&quot;,
            Effect: &quot;Allow&quot;,
            Resource: event.methodArn,
          },
        ],
      },
      context: {
        scope: verifiedData.scope, // Propagate scope downstream
      },
    }
    return authResponse
  } catch (err) {
    console.log(&quot;Authorizer Error: &quot;, err)
    throw new Error(&quot;Unauthorized&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;7-releasing-the-lambda-authorizer&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#7-releasing-the-lambda-authorizer&quot;&gt;7. Releasing the Lambda Authorizer&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Finally, add a release command to the &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;lambda-authorizers/package.json&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;amp;&amp;amp; exit 1&quot;,
    &quot;release&quot;: &quot;serverless deploy --stage prod&quot;
  },
  &quot;dependencies&quot;: {
    &quot;jsonwebtoken&quot;: &quot;^8.5.1&quot;,
    &quot;jwks-rsa&quot;: &quot;^1.5.1&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;serverless&quot;: &quot;^1.45.1&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To upload the Lambda to AWS, &lt;a href=&quot;https://portal.aws.amazon.com/billing/signup&quot;&gt;sign up&lt;/a&gt; and make sure you have your &lt;a href=&quot;https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html&quot;&gt;credentials configured&lt;/a&gt;.
Then release the Lambda by running &lt;code&gt;npm run release&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;npm run release output&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service lambda-authorizers.zip file to S3...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
Serverless: Stack update finished...
Service Information

service: lambda-authorizers
stage: prod
region: eu-central-1
stack: lambda-authorizers-prod
resources: 5
api keys:
  None
endpoints:
  None
functions:
  auth0VerifyBearer: lambda-authorizers-prod-auth0VerifyBearer
layers:
  None&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h4 id=&quot;finding-the-arn&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#finding-the-arn&quot;&gt;Finding the ARN&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Now go to the AWS Console and visit the “Lambda” service.
Find &lt;code&gt;lambda-authorizers-prod-auth0VerifyBearer&lt;/code&gt; under “Functions” and take note of the ARN in the top right corner.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;auth0VerifyBearer Lambda ARN&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2646&quot; height=&quot;452&quot; src=&quot;https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_Z1FjeRC.webp&quot; srcset=&quot;https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_ZWJ84H.webp 320w, https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_1XzRXw.webp 480w, https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_Z2x3Hfd.webp 672w, https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_Z1Cdzxl.webp 960w, https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_ZwYtpC.webp 1344w, https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_Z1FjeRC.webp 2646w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;auth0VerifyBearer Lambda ARN&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;We’ll need this to configure the Account API in the next part.&lt;/p&gt;
&lt;h2 id=&quot;implementing-the-account-api&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#implementing-the-account-api&quot;&gt;Implementing the Account API&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’ll do this by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#1-setting-up-the-api-project&quot;&gt;1. Setting up the API project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#2-configuring-the-serverless-manifest&quot;&gt;2. Configuring the Serverless manifest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#3-defining-the-lambda-handler&quot;&gt;3. Defining the Lambda handler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#4-releasing-the-api&quot;&gt;4. Releasing the API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#5-configuring-the-lambda-authorizer&quot;&gt;5. Configuring the Lambda Authorizer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#6-adding-authorization-logic&quot;&gt;6. Adding authorization logic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#7-releasing-the-api-with-auth-enabled&quot;&gt;7. Releasing the API with auth enabled&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#8-getting-a-test-token&quot;&gt;8. Getting a test token&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-setting-up-the-api-project&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#1-setting-up-the-api-project&quot;&gt;1. Setting up the API project&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Similar to the Lambda Authorizer, create a new directory for the code:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;mkdir account-api&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Move to this directory and initialize a new npm project with:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm init -y&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This creates a &lt;code&gt;package.json&lt;/code&gt; file.
Again, we’ll use the Serverless Framework to configure and upload the Lambda to AWS, so install it as a dev dependency:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm i -D serverless&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;2-configuring-the-serverless-manifest&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#2-configuring-the-serverless-manifest&quot;&gt;2. Configuring the Serverless manifest&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Create a Serverless manifest and add the Lambda function definition for the &lt;code&gt;GET /profile&lt;/code&gt; endpoint handler:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;account-api/serverless.yaml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: account-api

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage, &apos;prod&apos;}
  region: ${opt:region, &apos;eu-central-1&apos;}
  memorySize: 128
  timeout: 3

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src

functions:
  getProfile:
    handler: src/handler.getProfile
    description: Gets the user profile data
    events:
      - http:
          path: /profile
          method: get&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;3-defining-the-lambda-handler&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#3-defining-the-lambda-handler&quot;&gt;3. Defining the Lambda handler&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In order to match the Lambda function definition in the Serverless manifest, create a file named &lt;code&gt;handler.js&lt;/code&gt; in &lt;code&gt;src&lt;/code&gt;.
In that file export a method named &lt;code&gt;getProfile&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;account-api/src/handler.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

module.exports.getProfile = async () =&amp;gt; {
  try {
    // Lambda handler implementation goes here.
  } catch (err) {
    const statusCode = err.code || 500
    return {
      statusCode,
      body: JSON.stringify({
        message: err.message,
        info: err.info,
      }),
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;If something goes wrong in the Lambda, we’ll return an error response as &lt;a href=&quot;https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format&quot;&gt;HTTP output&lt;/a&gt; to the caller.&lt;/p&gt;
&lt;p&gt;Otherwise we’ll return the profile data:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;account-api/src/handler.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

module.exports.getProfile = async () =&amp;gt; {
  try {
    const profileData = {
      name: &quot;Daniël&quot;,
    }
    return {
      statusCode: 200,
      body: JSON.stringify(profileData),
    }
  } catch (err) {
    const statusCode = err.code || 500
    return {
      statusCode,
      body: JSON.stringify({
        message: err.message,
        info: err.info,
      }),
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Before we enable auth, let’s first release the API to see if we can call the endpoint.&lt;/p&gt;
&lt;h3 id=&quot;4-releasing-the-api&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#4-releasing-the-api&quot;&gt;4. Releasing the API&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Add a release command to the &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;account-api/package.json&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;amp;&amp;amp; exit 1&quot;,
    &quot;release&quot;: &quot;serverless deploy --stage prod&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;serverless&quot;: &quot;^1.45.1&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Then release the Lambda by running &lt;code&gt;npm run release&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;npm run release output&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service account-api.zip file to S3...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
Serverless: Stack update finished...
Service Information

service: account-api
stage: prod
region: eu-central-1
stack: account-api-prod
resources: 10
api keys:
  None
endpoints:
  GET - https://9jwh.execute-api.eu-central-1.amazonaws.com/prod/profile
functions:
  getProfile: account-api-prod-getProfile
layers:
  None&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Now try to call the endpoint that has been created for you.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;curl https://9jwh.execute-api.eu-central-1.amazonaws.com/prod/profile&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;It should return:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP response&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;200 OK
Content-Type: application/json

{
  &quot;name&quot;: &quot;Daniël&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;5-configuring-the-lambda-authorizer&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#5-configuring-the-lambda-authorizer&quot;&gt;5. Configuring the Lambda Authorizer&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now we know the endpoint is working, we’ll protect it by adding a custom &lt;code&gt;authorizer&lt;/code&gt; property in the &lt;code&gt;serverless.yaml&lt;/code&gt; manifest:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;account-api/serverless.yaml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: account-api

custom:
  authorizer:
    arn: LAMBDA_AUTHORIZER_ARN
    resultTtlInSeconds: 0
    identitySource: method.request.header.Authorization
    identityValidationExpression: &apos;^Bearer [A-Za-z0-9_.-]+$&apos;
    type: token

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage, &apos;prod&apos;}
  region: ${opt:region, &apos;eu-central-1&apos;}
  memorySize: 128
  timeout: 3

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src

functions:
  getProfile:
    handler: src/handler.getProfile
    description: Gets the user profile
    events:
      - http:
          path: /profile
          method: get
          authorizer: ${self:custom.authorizer}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Let’s go over the &lt;code&gt;authorizer&lt;/code&gt; properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;arn&lt;/code&gt;: must be the value of the Lambda Authorizer ARN we &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#finding-the-arn&quot;&gt;released&lt;/a&gt; before.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resultTtlInSeconds&lt;/code&gt;: used to cache the IAM Policy document returned from the Lambda Authorizer&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/serverless-auth#user-content-fn-5&quot; id=&quot;user-content-fnref-5&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;identitySource&lt;/code&gt;: where APIG should “look” for the bearer token.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;identityValidationExpression&lt;/code&gt;: the expression used to validate the &lt;code&gt;identitySource&lt;/code&gt; header before APIG invokes the authorizer. Our helper extracts the token; this expression only checks the header format.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;6-adding-authorization-logic&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#6-adding-authorization-logic&quot;&gt;6. Adding authorization logic&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that the Lambda Authorizer is configured and propagates the &lt;code&gt;get:profile&lt;/code&gt; scope, we can check if a caller has been granted the required scope.
If not, we’ll return a &lt;code&gt;403 Forbidden&lt;/code&gt; response to the caller:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;account-api/src/handler.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

const REQUIRED_SCOPE = &quot;get:profile&quot;

module.exports.getProfile = async (event) =&amp;gt; {
  try {
    const { authorizer = {} } = event.requestContext
    const { scope = &quot;&quot; } = authorizer
    const hasScope = scope.split(&quot; &quot;).includes(REQUIRED_SCOPE)
    if (!hasScope) {
      const err = new Error(&quot;Forbidden&quot;)
      err.code = 403
      err.info = &apos;scope &quot;get:profile&quot; is required&apos;
      throw err
    }

    const profileData = {
      name: &quot;Daniël&quot;,
    }
    return {
      statusCode: 200,
      body: JSON.stringify(profileData),
    }
  } catch (err) {
    const statusCode = err.code || 500
    return {
      statusCode,
      body: JSON.stringify({
        message: err.message,
        info: err.info,
      }),
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Note that the &lt;code&gt;authorizer.scope&lt;/code&gt; is a string and that it may contain more than one scope value.
When multiple scopes are configured, they will be space separated like this:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;authorizer.scope&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;get:profile update:profile&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;7-releasing-the-api-with-auth-enabled&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#7-releasing-the-api-with-auth-enabled&quot;&gt;7. Releasing the API with auth enabled&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Do another release by running &lt;code&gt;npm run release&lt;/code&gt;.
After Serverless finishes, go to the AWS Console and visit the “API Gateway” service.
Navigate to “prod-account-api” and click on the “GET” resource under “/profile”.
You should now see that the “Method Request” tile has a property “Auth” set to &lt;code&gt;auth0VerifyBearer&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;API Gateway Lambda authorizer configuration&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2646&quot; height=&quot;452&quot; src=&quot;https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_Z1FjeRC.webp&quot; srcset=&quot;https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_ZWJ84H.webp 320w, https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_1XzRXw.webp 480w, https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_Z2x3Hfd.webp 672w, https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_Z1Cdzxl.webp 960w, https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_ZwYtpC.webp 1344w, https://dans.land/assets/lambda-authorizer-arn.Cv9B9zzR_Z1FjeRC.webp 2646w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;API Gateway Lambda authorizer configuration&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This means our &lt;code&gt;GET /profile&lt;/code&gt; endpoint is properly configured with a Lambda Authorizer.
We now require a bearer token to get the profile data.
Let’s verify this by making the same &lt;code&gt;curl&lt;/code&gt; request like before (without a token):&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;curl https://9jwh.execute-api.eu-central-1.amazonaws.com/prod/profile&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;It should return:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP response&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;401 Unauthorized
Content-Type: application/json

{
  &quot;message&quot;: &quot;Unauthorized&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h3 id=&quot;8-getting-a-test-token&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#8-getting-a-test-token&quot;&gt;8. Getting a test token&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We can get a test token from the Auth0 dashboard by navigating to the “Test” tab in the API details screen.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Auth0 bearer token test command&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2134&quot; height=&quot;664&quot; src=&quot;https://dans.land/assets/test.CGKl1j4M_1D59fC.webp&quot; srcset=&quot;https://dans.land/assets/test.CGKl1j4M_4MC9Y.webp 320w, https://dans.land/assets/test.CGKl1j4M_1f0Ttt.webp 480w, https://dans.land/assets/test.CGKl1j4M_JnNBD.webp 672w, https://dans.land/assets/test.CGKl1j4M_1iMcTS.webp 960w, https://dans.land/assets/test.CGKl1j4M_1pGBPk.webp 1344w, https://dans.land/assets/test.CGKl1j4M_1D59fC.webp 2134w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Auth0 bearer token test command&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;If you scroll to the bottom, you’ll see a &lt;code&gt;curl&lt;/code&gt; command displayed with a ready to use test token:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;curl --request GET \
  --url http://path_to_your_api/ \
  --header &apos;authorization: Bearer eyJ...lKw&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Pretty cool, right?
Use this, but set the URL to your profile endpoint.
For example:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;curl --request GET \
  --url https://9jwh.execute-api.eu-central-1.amazonaws.com/prod/profile \
  --header &apos;authorization: Bearer eyJ...lKw&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This should return the profile data again:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP response&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;200 OK
Content-Type: application/json

{
  &quot;name&quot;: &quot;Daniël&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Also, sending a token &lt;em&gt;without&lt;/em&gt; the required scope should return a &lt;code&gt;403&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;HTTP response&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;403 Forbidden
Content-Type: application/json

{
  &quot;message&quot;: &quot;Error: Forbidden&quot;,
  &quot;info&quot;: &quot;scope \&quot;get:profile\&quot; is required&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Awesome!
We successfully secured our API with a token-based authentication strategy.
So only authenticated &lt;em&gt;and&lt;/em&gt; authorized clients can access it now!&lt;/p&gt;
&lt;h2 id=&quot;cors-headers&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#cors-headers&quot;&gt;CORS headers&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On a final note,
when your API needs to return &lt;a href=&quot;https://serverless.com/blog/cors-api-gateway-survival-guide&quot;&gt;CORS headers&lt;/a&gt;,
make sure to add a &lt;a href=&quot;https://docs.aws.amazon.com/apigateway/latest/developerguide/supported-gateway-response-types.html&quot;&gt;custom APIG Response&lt;/a&gt; as well:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;account-api/serverless.yaml&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;service: account-api

custom:
  authorizer:
    arn: LAMBDA_AUTHORIZER_ARN
    resultTtlInSeconds: 0
    identitySource: method.request.header.Authorization
    identityValidationExpression: &apos;^Bearer [A-Za-z0-9_.-]+$&apos;
    type: token

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage, &apos;prod&apos;}
  region: ${opt:region, &apos;eu-central-1&apos;}
  memorySize: 128
  timeout: 3

package:
  exclude:
    - ./*
    - ./**/*.test.js
  include:
    - node_modules
    - src

functions:
  getProfile:
    handler: src/handler.getProfile
    description: Gets the user profile
    events:
      - http:
          path: /profile
          method: get
          authorizer: ${self:custom.authorizer}

resources:
  Resources:
    GatewayResponseDefault4XX:
      Type: &quot;AWS::ApiGateway::GatewayResponse&quot;
      Properties:
        ResponseParameters:
          gatewayresponse.header.Access-Control-Allow-Origin: &quot;&apos;*&apos;&quot;
          gatewayresponse.header.Access-Control-Allow-Headers: &quot;&apos;*&apos;&quot;
        ResponseType: DEFAULT_4XX
        RestApiId:
          Ref: &quot;ApiGatewayRestApi&quot;
    GatewayResponseDefault5XX:
      Type: &quot;AWS::ApiGateway::GatewayResponse&quot;
      Properties:
        ResponseParameters:
          gatewayresponse.header.Access-Control-Allow-Origin: &quot;&apos;*&apos;&quot;
          gatewayresponse.header.Access-Control-Allow-Headers: &quot;&apos;*&apos;&quot;
        ResponseType: DEFAULT_5XX
        RestApiId:
          Ref: &quot;ApiGatewayRestApi&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;When the Lambda Authorizer throws an error or returns a “Deny” policy, APIG will &lt;em&gt;not&lt;/em&gt; execute any Lambda handlers.
This means that the CORS settings you added to the Lambda handler won’t be applied.
That’s why we must define additional APIG response resources, to make sure we always return the proper CORS headers.&lt;/p&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 id=&quot;footnote-label&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;You can see what a JWT looks like by visiting &lt;a href=&quot;https://jwt.io/&quot;&gt;jwt.io&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/lambda/serverless-auth#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;The “Identifier” doesn’t have to be a “real” endpoint. &lt;a href=&quot;https://dans.land/garden/lambda/serverless-auth#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-3&quot;&gt;
&lt;p&gt;We get the JWKS URI, issuer and audience values from the &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/serverless-auth#lambda-authorizer-configuration&quot;&gt;Lambda Authorizer configuration&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/lambda/serverless-auth#user-content-fnref-3&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 3&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-4&quot;&gt;
&lt;p&gt;The thrown error message &lt;em&gt;must&lt;/em&gt; match the string &lt;code&gt;&quot;Unauthorized&quot;&lt;/code&gt; &lt;em&gt;exactly&lt;/em&gt; for this to work. &lt;a href=&quot;https://dans.land/garden/lambda/serverless-auth#user-content-fnref-4&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 4&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-5&quot;&gt;
&lt;p&gt;Caching is &lt;em&gt;disabled&lt;/em&gt; when set to &lt;code&gt;0&lt;/code&gt;. When caching is enabled and a policy document has been cached, the Lambda Authorizer will &lt;em&gt;not&lt;/em&gt; be executed. According to the AWS &lt;a href=&quot;https://docs.aws.amazon.com/apigateway/latest/api/API_Authorizer.html&quot;&gt;docs&lt;/a&gt; the default value is &lt;code&gt;300&lt;/code&gt; seconds and the max value is &lt;code&gt;3600&lt;/code&gt; seconds. &lt;a href=&quot;https://dans.land/garden/lambda/serverless-auth#user-content-fnref-5&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 5&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item><item><title>Node.js event loop</title><link>https://dans.land/garden/lambda/nodejs-event-loop</link><guid isPermaLink="true">https://dans.land/garden/lambda/nodejs-event-loop</guid><description>AWS Lambda can freeze and thaw its execution context, which can impact Node.js event loop behavior.</description><pubDate>Thu, 30 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the more surprising things I learned recently while working with AWS Lambda is how it interacts with the Node.js event loop.&lt;/p&gt;
&lt;p&gt;Lambda is powered by a &lt;a href=&quot;https://aws.amazon.com/blogs/aws/firecracker-lightweight-virtualization-for-serverless-computing&quot;&gt;virtualization technology&lt;/a&gt;,
and to optimize performance it can “freeze” and “thaw” the execution context of your code so it can be reused.&lt;/p&gt;
&lt;p&gt;This can avoid initialization work, but can also impact the expected event loop behavior.
We’ll explore this in detail.
But let’s quickly refresh the Node.js concurrency model.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Already familiar with the event loop?&lt;/p&gt;
&lt;p&gt;Go straight to the &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#aws-lambda&quot;&gt;AWS Lambda&lt;/a&gt; section.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;concurrency-model&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#concurrency-model&quot;&gt;Concurrency model&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Node.js is &lt;em&gt;single threaded&lt;/em&gt; and the &lt;a href=&quot;https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick&quot;&gt;event loop&lt;/a&gt; is the concurrency model that allows non-blocking I/O operations to be performed by offloading operations to the system kernel whenever possible.&lt;/p&gt;
&lt;p&gt;How? Well, we’ll have to discuss the call stack and the task queue first.&lt;/p&gt;
&lt;h3 id=&quot;call-stack&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#call-stack&quot;&gt;Call stack&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Function calls form a &lt;em&gt;stack of frames&lt;/em&gt;, where each frame represents a single function call.&lt;/p&gt;
&lt;p&gt;Every time a function is called, it’s &lt;em&gt;pushed&lt;/em&gt; onto the stack (i.e. added to the stack).
When the function is done executing, it’s &lt;em&gt;popped&lt;/em&gt; off the stack (i.e. removed from the stack).&lt;/p&gt;
&lt;p&gt;The frames in a stack are popped off in &lt;abbr title=&quot;Last In First Out&quot;&gt;LIFO&lt;/abbr&gt; order.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Call stack frames are added last and removed first&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/call-stack.C5zQF7i9_BSuL9.webp&quot; srcset=&quot;https://dans.land/assets/call-stack.C5zQF7i9_ZBD5Gh.webp 320w, https://dans.land/assets/call-stack.C5zQF7i9_lXrNm.webp 480w, https://dans.land/assets/call-stack.C5zQF7i9_Z1qSzz6.webp 672w, https://dans.land/assets/call-stack.C5zQF7i9_1ivzsg.webp 960w, https://dans.land/assets/call-stack.C5zQF7i9_Z26GWS6.webp 1344w, https://dans.land/assets/call-stack.C5zQF7i9_BSuL9.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Call stack frames are added last and removed first&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Each frame stores information about the invoked function.
Like the arguments the function was called with and any variables defined inside the called function’s body.&lt;/p&gt;
&lt;p&gt;When we execute the following code:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function work() {
  console.log(&quot;do work&quot;)
}

function main() {
  console.log(&quot;main start&quot;)
  work()
  console.log(&quot;main end&quot;)
}

main()&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;We can visualize the call stack over time like this.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Call stack states 1-3: empty, main, then console above main&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/1.BGYS8FSi_1P51sA.webp&quot; srcset=&quot;https://dans.land/assets/1.BGYS8FSi_Z290m7T.webp 320w, https://dans.land/assets/1.BGYS8FSi_Z1aL6j0.webp 480w, https://dans.land/assets/1.BGYS8FSi_Z1KGyk3.webp 672w, https://dans.land/assets/1.BGYS8FSi_17XTSE.webp 960w, https://dans.land/assets/1.BGYS8FSi_1sfTvL.webp 1344w, https://dans.land/assets/1.BGYS8FSi_1P51sA.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Call stack states 1-3: empty, main, then console above main&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;When the script starts executing, the call stack is empty.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main()&lt;/code&gt; is called and pushed onto the call stack:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function work() {
  console.log(&quot;do work&quot;)
}

function main() {
  console.log(&quot;main start&quot;)
  work()
  console.log(&quot;main end&quot;)
}

main()&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While executing &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;console.log(&quot;main start&quot;)&lt;/code&gt; is called and pushed onto the call stack:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function work() {
  console.log(&quot;do work&quot;)
}

function main() {
  console.log(&quot;main start&quot;)
  work()
  console.log(&quot;main end&quot;)
}

main()&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Call stack states 4-6: main, work above main, then console above both&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1200&quot; height=&quot;900&quot; src=&quot;https://dans.land/assets/2.CfHFFGJr_2lGTDp.webp&quot; srcset=&quot;https://dans.land/assets/2.CfHFFGJr_bM9hU.webp 320w, https://dans.land/assets/2.CfHFFGJr_1gIA37.webp 480w, https://dans.land/assets/2.CfHFFGJr_ZbmGYB.webp 672w, https://dans.land/assets/2.CfHFFGJr_Z2tUbOa.webp 960w, https://dans.land/assets/2.CfHFFGJr_2lGTDp.webp 1200w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Call stack states 4-6: main, work above main, then console above both&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;console.log&lt;/code&gt; executes, prints &lt;code&gt;main start&lt;/code&gt; and is popped off the call stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; continues executing and calls &lt;code&gt;work()&lt;/code&gt;, which is pushed onto the call stack:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function work() {
  console.log(&quot;do work&quot;)
}

function main() {
  console.log(&quot;main start&quot;)
  work()
  console.log(&quot;main end&quot;)
}

main()&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While executing &lt;code&gt;work&lt;/code&gt;, &lt;code&gt;console.log(&quot;do work&quot;)&lt;/code&gt; is called and pushed onto the call stack:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function work() {
  console.log(&quot;do work&quot;)
}

function main() {
  console.log(&quot;main start&quot;)
  work()
  console.log(&quot;main end&quot;)
}

main()&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Call stack states 7-9: work above main, main, then console above main&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/3.DnaOfD52_Z2gQ4BF.webp&quot; srcset=&quot;https://dans.land/assets/3.DnaOfD52_Z1i3Mad.webp 320w, https://dans.land/assets/3.DnaOfD52_ZjOwlj.webp 480w, https://dans.land/assets/3.DnaOfD52_ZTJYmm.webp 672w, https://dans.land/assets/3.DnaOfD52_1XUtQl.webp 960w, https://dans.land/assets/3.DnaOfD52_2qvWfr.webp 1344w, https://dans.land/assets/3.DnaOfD52_Z2gQ4BF.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Call stack states 7-9: work above main, main, then console above main&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;console.log&lt;/code&gt; executes, prints &lt;code&gt;do work&lt;/code&gt; and is popped off the call stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;work&lt;/code&gt; finishes executing and is popped off the call stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; continues executing and calls &lt;code&gt;console.log(&quot;main end&quot;)&lt;/code&gt;, which is pushed onto the call stack:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function work() {
  console.log(&quot;do work&quot;)
}

function main() {
  console.log(&quot;main start&quot;)
  work()
  console.log(&quot;main end&quot;)
}

main()&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Call stack states 10-11: main, then an empty stack&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/4.BYwergx2_3DYjX.webp&quot; srcset=&quot;https://dans.land/assets/4.BYwergx2_Z4pCaQ.webp 320w, https://dans.land/assets/4.BYwergx2_SNCD3.webp 480w, https://dans.land/assets/4.BYwergx2_iSaC0.webp 672w, https://dans.land/assets/4.BYwergx2_Z1RCtXe.webp 960w, https://dans.land/assets/4.BYwergx2_Zja7BQ.webp 1344w, https://dans.land/assets/4.BYwergx2_3DYjX.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Call stack states 10-11: main, then an empty stack&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;console.log&lt;/code&gt; executes, prints &lt;code&gt;main end&lt;/code&gt; and is popped off the call stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; finishes executing and is popped off the call stack. The call stack is empty again and the script finishes executing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This code didn’t interact with any asynchronous (internal) APIs.
But when it does (like when calling &lt;code&gt;setTimeout(callback)&lt;/code&gt;) it makes use of the task queue.&lt;/p&gt;
&lt;h3 id=&quot;task-queue&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#task-queue&quot;&gt;Task queue&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this simplified model, asynchronous work in the runtime is represented as a task in a queue. Or in other words, a &lt;em&gt;message queue&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Each message can be thought of as a function that will be called in &lt;abbr title=&quot;First In First Out&quot;&gt;FIFO&lt;/abbr&gt; order to handle said work.
One example is a &lt;code&gt;setTimeout&lt;/code&gt; callback whose delay has elapsed.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Task queue processes tasks in first-in, first-out order&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/queue.BjPJndMB_ZUnoG3.webp&quot; srcset=&quot;https://dans.land/assets/queue.BjPJndMB_hf8wr.webp 320w, https://dans.land/assets/queue.BjPJndMB_9ifRg.webp 480w, https://dans.land/assets/queue.BjPJndMB_ZpmnNF.webp 672w, https://dans.land/assets/queue.BjPJndMB_29lvtK.webp 960w, https://dans.land/assets/queue.BjPJndMB_ZFMYrx.webp 1344w, https://dans.land/assets/queue.BjPJndMB_ZUnoG3.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Task queue processes tasks in first-in, first-out order&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Additionally, each message is processed &lt;em&gt;completely&lt;/em&gt; before any other message is processed.
This means that &lt;strong&gt;whenever a function runs it can’t be interrupted&lt;/strong&gt;.
This behavior is called &lt;em&gt;run-to-completion&lt;/em&gt; and makes it easier to reason about our JavaScript programs.&lt;/p&gt;
&lt;p&gt;Messages get &lt;em&gt;enqueued&lt;/em&gt; (i.e. added to the queue) and at some point messages will be &lt;em&gt;dequeued&lt;/em&gt; (i.e. removed from the queue).&lt;/p&gt;
&lt;p&gt;When? How? This is handled by the event loop.&lt;/p&gt;
&lt;h3 id=&quot;event-loop&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#event-loop&quot;&gt;Event loop&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The event loop can be thought of as a loop, where every cycle is referred to as a &lt;em&gt;tick&lt;/em&gt;.
The event loop checks if there’s any work ready to run.
If there is, it executes the corresponding callback, &lt;strong&gt;but only if the call stack is empty&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A simplified event loop can be described with the following pseudo code&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;while (queue.waitForMessage()) {
  queue.processNextMessage()
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;To summarize:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When code executes, function calls are added to the call stack.&lt;/li&gt;
&lt;li&gt;Calls to asynchronous APIs like &lt;code&gt;setTimeout&lt;/code&gt; register work whose callbacks can run later.&lt;/li&gt;
&lt;li&gt;When the call stack is empty, ready callbacks can be pushed onto the call stack and executed. Node.js may process several callbacks in one tick.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class=&quot;callout callout--warning&quot; data-callout=&quot;warning&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;!&lt;/span&gt;Warning&lt;/p&gt;
&lt;p&gt;&lt;code&gt;new Promise(callback)&lt;/code&gt; runs its callback (called the &lt;em&gt;executor&lt;/em&gt;) immediately.
It’s the handlers passed to &lt;code&gt;.then()&lt;/code&gt; that run later.
See the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise&quot;&gt;Promise&lt;/a&gt; docs for more information.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Event loop moves queued tasks to an empty call stack&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/event-loop.BYiWmql6_wO404.webp&quot; srcset=&quot;https://dans.land/assets/event-loop.BYiWmql6_2du02.webp 320w, https://dans.land/assets/event-loop.BYiWmql6_10P2uF.webp 480w, https://dans.land/assets/event-loop.BYiWmql6_ZM1YRM.webp 672w, https://dans.land/assets/event-loop.BYiWmql6_1Wna9z.webp 960w, https://dans.land/assets/event-loop.BYiWmql6_Z2bLoEb.webp 1344w, https://dans.land/assets/event-loop.BYiWmql6_wO404.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Event loop moves queued tasks to an empty call stack&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;With that covered, we can explore how the AWS Lambda execution environment interacts with the Node.js event loop.&lt;/p&gt;
&lt;h2 id=&quot;aws-lambda&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#aws-lambda&quot;&gt;AWS Lambda&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AWS Lambda invokes a Lambda function via an exported handler function, e.g. &lt;code&gt;exports.handler&lt;/code&gt;.
For callback-based handlers, Lambda calls it with 3 arguments:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;handler(event, context, callback)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;callback&lt;/code&gt; argument may be used to return information to the caller and to signal that the handler function has completed, so Lambda may end it.
Our handler is &lt;code&gt;async&lt;/code&gt;, so we don’t call &lt;code&gt;callback&lt;/code&gt; ourselves.
Lambda uses the handler’s returned promise to determine when it has finished&lt;sup&gt;&lt;a href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#user-content-fn-2&quot; id=&quot;user-content-fnref-2&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id=&quot;baseline&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#baseline&quot;&gt;Baseline&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;From here on we’ll use a simple script as a “baseline” to reason about the event loop behavior.
Create a file called &lt;code&gt;timeout.js&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;timeout.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function timeout(ms) {
  console.log(&quot;timeout start&quot;)

  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`timeout cb fired after ${ms} ms`)
      resolve()
    }, ms)
  })
}

async function main() {
  console.log(&quot;main start&quot;)
  timeout(5e3)
  console.log(&quot;main end&quot;)
}

main()&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;When we execute this script &lt;em&gt;locally&lt;/em&gt; (not via Lambda) with &lt;code&gt;node timeout.js&lt;/code&gt;, the following will print:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;node timeout.js output&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;main start
timeout start
main end
timeout cb fired after 5000 ms&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The last message takes ~5 seconds to print, but the script does &lt;em&gt;not&lt;/em&gt; stop executing before it does.
The timer keeps the Node.js process alive, even though &lt;code&gt;main&lt;/code&gt; has returned.&lt;/p&gt;
&lt;h3 id=&quot;what-happens-in-lambda-stays-in-lambda&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#what-happens-in-lambda-stays-in-lambda&quot;&gt;What happens in Lambda, stays in Lambda&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now let’s modify the code from &lt;code&gt;timeout.js&lt;/code&gt; so it’s compatible with Lambda:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;timeout.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function timeout(ms) {
  console.log(&quot;timeout start&quot;)

  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`timeout cb fired after ${ms} ms`)
      resolve()
    }, ms)
  })
}

async function main() {
  console.log(&quot;main start&quot;)
  timeout(5e3)
  console.log(&quot;main end&quot;)
}

exports.handler = main&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;You can create a new function in the AWS Lambda console and paste in the code from above.
Use a CommonJS file called &lt;code&gt;timeout.js&lt;/code&gt;, configure the handler as &lt;code&gt;timeout.handler&lt;/code&gt;, and give the function a timeout longer than five seconds.
Run it, sit back and enjoy.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;First Lambda invocation ends before the timeout callback runs&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2600&quot; height=&quot;1666&quot; src=&quot;https://dans.land/assets/1.D37puKbe_Z1nLFCu.webp&quot; srcset=&quot;https://dans.land/assets/1.D37puKbe_Z3cL4F.webp 320w, https://dans.land/assets/1.D37puKbe_96KQb.webp 480w, https://dans.land/assets/1.D37puKbe_Z1SaTrr.webp 672w, https://dans.land/assets/1.D37puKbe_ZPKHP1.webp 960w, https://dans.land/assets/1.D37puKbe_1zcujf.webp 1344w, https://dans.land/assets/1.D37puKbe_Z1nLFCu.webp 2600w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;First Lambda invocation ends before the timeout callback runs&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Wait, what? Lambda just ended the handler function &lt;em&gt;without&lt;/em&gt; printing the last message &lt;code&gt;timeout cb fired after 5000 ms&lt;/code&gt;.
Let’s run it again.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Second Lambda invocation runs the previous timeout callback first&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2600&quot; height=&quot;1667&quot; src=&quot;https://dans.land/assets/2.D8HfkFFF_Z2qUlTz.webp&quot; srcset=&quot;https://dans.land/assets/2.D8HfkFFF_ZfyPzY.webp 320w, https://dans.land/assets/2.D8HfkFFF_Z3fiE8.webp 480w, https://dans.land/assets/2.D8HfkFFF_Z25wXWK.webp 672w, https://dans.land/assets/2.D8HfkFFF_Z1cPHKU.webp 960w, https://dans.land/assets/2.D8HfkFFF_w3O2a.webp 1344w, https://dans.land/assets/2.D8HfkFFF_Z2qUlTz.webp 2600w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Second Lambda invocation runs the previous timeout callback first&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;It now prints &lt;code&gt;timeout cb fired after 5000 ms&lt;/code&gt; &lt;em&gt;first&lt;/em&gt; and then the other ones!
So what’s going on here?&lt;/p&gt;
&lt;h3 id=&quot;aws-lambda-execution-model&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#aws-lambda-execution-model&quot;&gt;AWS Lambda execution model&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;AWS Lambda takes care of provisioning and managing resources needed to run your functions.
When a new execution context is needed, Lambda creates one for you based on the configuration you provide.
The execution context is a temporary runtime environment that initializes any external dependencies of your Lambda function.&lt;/p&gt;
&lt;p&gt;After a Lambda function is called, Lambda maintains the execution context for some time in anticipation of another invocation of the Lambda function (for performance benefits).
It freezes the execution context after a Lambda function completes and may choose to reuse (thaw) the same execution context when the Lambda function is called again (but it doesn’t have to).&lt;/p&gt;
&lt;p&gt;Our handler returns a promise because it’s an &lt;code&gt;async&lt;/code&gt; function.
Lambda can complete the invocation when that promise settles.
Calling &lt;code&gt;timeout(5e3)&lt;/code&gt; creates a separate promise, which &lt;code&gt;main&lt;/code&gt; ignores.
Since &lt;code&gt;main&lt;/code&gt; doesn’t await it, the handler can finish before the timer fires.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/nodejs-context.html&quot;&gt;&lt;code&gt;callbackWaitsForEmptyEventLoop&lt;/code&gt;&lt;/a&gt; setting controls callback-based completion;
it doesn’t make this async handler wait for the ignored promise.&lt;/p&gt;
&lt;p&gt;Okay, so with this information we can make sense of what happened when we executed the code in &lt;code&gt;timeout.js&lt;/code&gt; before.
Let’s break it down and go over it step by step.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 1: empty call stack and task queue&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/1.DJQmPgmq_1CRRLs.webp&quot; srcset=&quot;https://dans.land/assets/1.DJQmPgmq_ZFkPkl.webp 320w, https://dans.land/assets/1.DJQmPgmq_hSpty.webp 480w, https://dans.land/assets/1.DJQmPgmq_Zi22wu.webp 672w, https://dans.land/assets/1.DJQmPgmq_Z2txH7I.webp 960w, https://dans.land/assets/1.DJQmPgmq_1g3KOD.webp 1344w, https://dans.land/assets/1.DJQmPgmq_1CRRLs.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 1: empty call stack and task queue&lt;/figcaption&gt;&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Lambda starts executing the code in &lt;code&gt;timeout.js&lt;/code&gt;. The call stack is empty.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 2: main is pushed onto the call stack&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/2.Ba1wifrm_tBlph.webp&quot; srcset=&quot;https://dans.land/assets/2.Ba1wifrm_15sbJQ.webp 320w, https://dans.land/assets/2.Ba1wifrm_23GryK.webp 480w, https://dans.land/assets/2.Ba1wifrm_1sKYxH.webp 672w, https://dans.land/assets/2.Ba1wifrm_ZHJF2w.webp 960w, https://dans.land/assets/2.Ba1wifrm_6Mess.webp 1344w, https://dans.land/assets/2.Ba1wifrm_tBlph.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 2: main is pushed onto the call stack&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; is called and pushed onto the call stack:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;timeout.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function timeout(ms) {
  console.log(&quot;timeout start&quot;)

  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`timeout cb fired after ${ms} ms`)
      resolve()
    }, ms)
  })
}

async function main() {
  console.log(&quot;main start&quot;)
  timeout(5e3)
  console.log(&quot;main end&quot;)
}

exports.handler = main&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 3: console is pushed above main&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/3.-92DC9Up_Z2evWmt.webp&quot; srcset=&quot;https://dans.land/assets/3.-92DC9Up_1O8ewG.webp 320w, https://dans.land/assets/3.-92DC9Up_Z2hODsl.webp 480w, https://dans.land/assets/3.-92DC9Up_2cr2kx.webp 672w, https://dans.land/assets/3.-92DC9Up_UmJj.webp 960w, https://dans.land/assets/3.-92DC9Up_2sQ4uD.webp 1344w, https://dans.land/assets/3.-92DC9Up_Z2evWmt.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 3: console is pushed above main&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While executing &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;console.log(&quot;main start&quot;)&lt;/code&gt; is called and pushed onto the call stack:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;timeout.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function timeout(ms) {
  console.log(&quot;timeout start&quot;)

  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`timeout cb fired after ${ms} ms`)
      resolve()
    }, ms)
  })
}

async function main() {
  console.log(&quot;main start&quot;)
  timeout(5e3)
  console.log(&quot;main end&quot;)
}

exports.handler = main&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 4: console is popped, leaving main&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/4.DHhVZNdY_Z1UfxQS.webp&quot; srcset=&quot;https://dans.land/assets/4.DHhVZNdY_ZMl6V2.webp 320w, https://dans.land/assets/4.DHhVZNdY_aS8RR.webp 480w, https://dans.land/assets/4.DHhVZNdY_Zp2j8b.webp 672w, https://dans.land/assets/4.DHhVZNdY_2tDa5w.webp 960w, https://dans.land/assets/4.DHhVZNdY_Z2i4ENH.webp 1344w, https://dans.land/assets/4.DHhVZNdY_Z1UfxQS.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 4: console is popped, leaving main&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;console.log&lt;/code&gt; executes, prints &lt;code&gt;main start&lt;/code&gt; and is popped off the call stack.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 5: timeout is pushed above main&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/5.BBeiokN5_18sljt.webp&quot; srcset=&quot;https://dans.land/assets/5.BBeiokN5_ZmiTJz.webp 320w, https://dans.land/assets/5.BBeiokN5_AUl4k.webp 480w, https://dans.land/assets/5.BBeiokN5_YS3h.webp 672w, https://dans.land/assets/5.BBeiokN5_Z2avLwW.webp 960w, https://dans.land/assets/5.BBeiokN5_KDemE.webp 1344w, https://dans.land/assets/5.BBeiokN5_18sljt.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 5: timeout is pushed above main&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; continues executing and calls &lt;code&gt;timeout(5e3)&lt;/code&gt;, which is pushed onto the call stack:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;timeout.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function timeout(ms) {
  console.log(&quot;timeout start&quot;)

  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`timeout cb fired after ${ms} ms`)
      resolve()
    }, ms)
  })
}

async function main() {
  console.log(&quot;main start&quot;)
  timeout(5e3)
  console.log(&quot;main end&quot;)
}

exports.handler = main&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 6: console is pushed above timeout and main&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/6.DwPh57DN_ZJo907.webp&quot; srcset=&quot;https://dans.land/assets/6.DwPh57DN_e0CQI.webp 320w, https://dans.land/assets/6.DwPh57DN_1ceSFC.webp 480w, https://dans.land/assets/6.DwPh57DN_BjqEz.webp 672w, https://dans.land/assets/6.DwPh57DN_Z1zcdUE.webp 960w, https://dans.land/assets/6.DwPh57DN_Z17dfVV.webp 1344w, https://dans.land/assets/6.DwPh57DN_ZJo907.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 6: console is pushed above timeout and main&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While executing &lt;code&gt;timeout&lt;/code&gt;, &lt;code&gt;console.log(&quot;timeout start&quot;)&lt;/code&gt; is called and pushed onto the call stack:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;timeout.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function timeout(ms) {
  console.log(&quot;timeout start&quot;)

  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`timeout cb fired after ${ms} ms`)
      resolve()
    }, ms)
  })
}

async function main() {
  console.log(&quot;main start&quot;)
  timeout(5e3)
  console.log(&quot;main end&quot;)
}

exports.handler = main&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 7: console is popped, leaving timeout and main&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/7.Djqm-ssx_1RjMPS.webp&quot; srcset=&quot;https://dans.land/assets/7.Djqm-ssx_ZORNNm.webp 320w, https://dans.land/assets/7.Djqm-ssx_8lr0x.webp 480w, https://dans.land/assets/7.Djqm-ssx_Zrz10v.webp 672w, https://dans.land/assets/7.Djqm-ssx_2r6sdc.webp 960w, https://dans.land/assets/7.Djqm-ssx_1uuFT4.webp 1344w, https://dans.land/assets/7.Djqm-ssx_1RjMPS.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 7: console is popped, leaving timeout and main&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;console.log&lt;/code&gt; executes, prints &lt;code&gt;timeout start&lt;/code&gt; and is popped off the call stack.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 8: the Promise constructor immediately calls the executor&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1448&quot; height=&quot;1086&quot; src=&quot;https://dans.land/assets/8.Bem39S0m_PVbl8.webp&quot; srcset=&quot;https://dans.land/assets/8.Bem39S0m_Z1dDcYi.webp 320w, https://dans.land/assets/8.Bem39S0m_ZfoWao.webp 480w, https://dans.land/assets/8.Bem39S0m_ZPkpbr.webp 672w, https://dans.land/assets/8.Bem39S0m_23l42g.webp 960w, https://dans.land/assets/8.Bem39S0m_ZoFj12.webp 1344w, https://dans.land/assets/8.Bem39S0m_PVbl8.webp 1448w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 8: the Promise constructor immediately calls the executor&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;timeout&lt;/code&gt; continues executing and invokes the &lt;code&gt;Promise&lt;/code&gt; constructor on line 6. The constructor immediately calls the executor (i.e. the callback), which is pushed onto the call stack:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;timeout.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function timeout(ms) {
  console.log(&quot;timeout start&quot;)

  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`timeout cb fired after ${ms} ms`)
      resolve()
    }, ms)
  })
}

async function main() {
  console.log(&quot;main start&quot;)
  timeout(5e3)
  console.log(&quot;main end&quot;)
}

exports.handler = main&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 9: the executor calls setTimeout and registers the timer&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1448&quot; height=&quot;1086&quot; src=&quot;https://dans.land/assets/9.qEd-duIa_ZusJ2w.webp&quot; srcset=&quot;https://dans.land/assets/9.qEd-duIa_Z2fkI3O.webp 320w, https://dans.land/assets/9.qEd-duIa_Z1h6seU.webp 480w, https://dans.land/assets/9.qEd-duIa_Z1R1UfX.webp 672w, https://dans.land/assets/9.qEd-duIa_11DxWJ.webp 960w, https://dans.land/assets/9.qEd-duIa_Z1K5eoG.webp 1344w, https://dans.land/assets/9.qEd-duIa_ZusJ2w.webp 1448w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 9: the executor calls setTimeout and registers the timer&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The executor calls &lt;code&gt;setTimeout&lt;/code&gt; on line 7, which registers the timer and its callback. After the delay, the timer callback can run when the call stack is empty:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;timeout.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function timeout(ms) {
  console.log(&quot;timeout start&quot;)

  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`timeout cb fired after ${ms} ms`)
      resolve()
    }, ms)
  })
}

async function main() {
  console.log(&quot;main start&quot;)
  timeout(5e3)
  console.log(&quot;main end&quot;)
}

exports.handler = main&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 10: timeout returns a pending promise to main while the timer remains registered&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1448&quot; height=&quot;1086&quot; src=&quot;https://dans.land/assets/10.CYQ1qIOd_ZobPYw.webp&quot; srcset=&quot;https://dans.land/assets/10.CYQ1qIOd_1nomhu.webp 320w, https://dans.land/assets/10.CYQ1qIOd_Z13iV0K.webp 480w, https://dans.land/assets/10.CYQ1qIOd_Z2IbgD.webp 672w, https://dans.land/assets/10.CYQ1qIOd_1kFFpi.webp 960w, https://dans.land/assets/10.CYQ1qIOd_2u7iD3.webp 1344w, https://dans.land/assets/10.CYQ1qIOd_ZobPYw.webp 1448w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 10: timeout returns a pending promise to main while the timer remains registered&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;setTimeout&lt;/code&gt; returns, then the executor returns. The constructor returns a pending promise, and &lt;code&gt;timeout&lt;/code&gt; returns that promise to &lt;code&gt;main&lt;/code&gt;. Only &lt;code&gt;main&lt;/code&gt; remains on the call stack. It ignores the returned promise and continues immediately.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 11: console is pushed above main while the timer remains registered&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1448&quot; height=&quot;1086&quot; src=&quot;https://dans.land/assets/11.B8ThGeiy_SdTFX.webp&quot; srcset=&quot;https://dans.land/assets/11.B8ThGeiy_Z2bcVeD.webp 320w, https://dans.land/assets/11.B8ThGeiy_sgTh3.webp 480w, https://dans.land/assets/11.B8ThGeiy_1sQE1a.webp 672w, https://dans.land/assets/11.B8ThGeiy_Z2dUC6P.webp 960w, https://dans.land/assets/11.B8ThGeiy_Z1iE4uo.webp 1344w, https://dans.land/assets/11.B8ThGeiy_SdTFX.webp 1448w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 11: console is pushed above main while the timer remains registered&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; continues executing and calls &lt;code&gt;console.log(&quot;main end&quot;)&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;timeout.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function timeout(ms) {
  console.log(&quot;timeout start&quot;)

  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`timeout cb fired after ${ms} ms`)
      resolve()
    }, ms)
  })
}

async function main() {
  console.log(&quot;main start&quot;)
  timeout(5e3)
  console.log(&quot;main end&quot;)
}

exports.handler = main&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;State 12: main has returned, its promise is fulfilled, and the timer promise is still pending&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1448&quot; height=&quot;1086&quot; src=&quot;https://dans.land/assets/12.D7K3LU1c_2tvUkb.webp&quot; srcset=&quot;https://dans.land/assets/12.D7K3LU1c_Zu0yGw.webp 320w, https://dans.land/assets/12.D7K3LU1c_29tgOa.webp 480w, https://dans.land/assets/12.D7K3LU1c_Z1U87fE.webp 672w, https://dans.land/assets/12.D7K3LU1c_ZwIfyI.webp 960w, https://dans.land/assets/12.D7K3LU1c_hCV8O.webp 1344w, https://dans.land/assets/12.D7K3LU1c_2tvUkb.webp 1448w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;State 12: main has returned, its promise is fulfilled, and the timer promise is still pending&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;console.log&lt;/code&gt; prints &lt;code&gt;main end&lt;/code&gt; and is popped off the call stack. Then &lt;code&gt;main&lt;/code&gt; finishes executing and is popped off too. Its returned promise is fulfilled, while the promise returned by &lt;code&gt;timeout&lt;/code&gt; is still pending.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point the call stack is empty and the timer callback isn’t ready yet.
The timer would keep a local Node.js process alive, but Lambda can complete this invocation because the handler’s returned promise has fulfilled.
So it can &lt;em&gt;freeze&lt;/em&gt; the process and return results to the caller!&lt;/p&gt;
&lt;p&gt;The interesting part here is that Lambda doesn’t immediately destroy its execution context.
In my experiment, waiting for +5 seconds and running the Lambda again (like in the &lt;a class=&quot;wikilink&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#what-happens-in-lambda-stays-in-lambda&quot;&gt;second run&lt;/a&gt;) printed the &lt;code&gt;setTimeout&lt;/code&gt; callback’s message first.&lt;/p&gt;
&lt;p&gt;The execution context was still around, but JavaScript wasn’t running while it was frozen.
When Lambda reused it, the timer’s delay had already elapsed, so its callback was ready to run:&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Resumed execution context: the overdue timer callback is ready to run&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1448&quot; height=&quot;1086&quot; src=&quot;https://dans.land/assets/exec-context-1.DzX54PoT_avyuA.webp&quot; srcset=&quot;https://dans.land/assets/exec-context-1.DzX54PoT_1CEtBB.webp 320w, https://dans.land/assets/exec-context-1.DzX54PoT_Z2J6V2.webp 480w, https://dans.land/assets/exec-context-1.DzX54PoT_ZvA3tU.webp 672w, https://dans.land/assets/exec-context-1.DzX54PoT_2kqX7N.webp 960w, https://dans.land/assets/exec-context-1.DzX54PoT_1yqBFL.webp 1344w, https://dans.land/assets/exec-context-1.DzX54PoT_avyuA.webp 1448w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Resumed execution context: the overdue timer callback is ready to run&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;The event loop could then push the callback onto the call stack:&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Thawed execution context: the timeout callback moves onto the call stack&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/exec-context-2.qggrGngj_ZRamXF.webp&quot; srcset=&quot;https://dans.land/assets/exec-context-2.qggrGngj_ZAelKV.webp 320w, https://dans.land/assets/exec-context-2.qggrGngj_Z2gCWjz.webp 480w, https://dans.land/assets/exec-context-2.qggrGngj_2kHeVt.webp 672w, https://dans.land/assets/exec-context-2.qggrGngj_6x7Jg.webp 960w, https://dans.land/assets/exec-context-2.qggrGngj_Z1nPBCQ.webp 1344w, https://dans.land/assets/exec-context-2.qggrGngj_ZRamXF.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Thawed execution context: the timeout callback moves onto the call stack&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This resulted in &lt;code&gt;timeout cb fired after 5000 ms&lt;/code&gt; being printed first, because in this run it executed before any of the code in our Lambda handler:&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Reused execution context: console runs above the previous timeout callback&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1440&quot; height=&quot;1080&quot; src=&quot;https://dans.land/assets/exec-context-3.BZXdTDj8_ZJC2WI.webp&quot; srcset=&quot;https://dans.land/assets/exec-context-3.BZXdTDj8_2m7xbB.webp 320w, https://dans.land/assets/exec-context-3.BZXdTDj8_FHVCX.webp 480w, https://dans.land/assets/exec-context-3.BZXdTDj8_cR055.webp 672w, https://dans.land/assets/exec-context-3.BZXdTDj8_Z21i778.webp 960w, https://dans.land/assets/exec-context-3.BZXdTDj8_Z1gihBT.webp 1344w, https://dans.land/assets/exec-context-3.BZXdTDj8_ZJC2WI.webp 1440w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Reused execution context: console runs above the previous timeout callback&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Neither reuse nor this ordering is guaranteed.
If Lambda discards the environment, that unfinished callback never runs.
See the AWS &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html&quot;&gt;Lambda runtime environment&lt;/a&gt; docs for more information.&lt;/p&gt;
&lt;h3 id=&quot;doing-it-right&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#doing-it-right&quot;&gt;Doing it right&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Obviously this is undesired behavior and you should &lt;em&gt;not&lt;/em&gt; write your code in the same way we wrote the code in &lt;code&gt;timeout.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As stated in the AWS docs, we need to make sure to complete processing &lt;em&gt;all&lt;/em&gt; callbacks before our handler exits:&lt;/p&gt;
&lt;blockquote cite=&quot;https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html&quot;&gt;
&lt;p&gt;Make sure that any background processes or callbacks in your code are complete before the code exits.&lt;/p&gt;
&lt;footer&gt;&lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html&quot;&gt;https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html&lt;/a&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;p&gt;Therefore we’ll make the following change to the code in &lt;code&gt;timeout.js&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;pre&gt;&lt;code class=&quot;language-diff&quot;&gt;- timeout(5e3);
+ await timeout(5e3);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;This change makes sure the handler function does &lt;em&gt;not&lt;/em&gt; finish until the promise returned by &lt;code&gt;timeout&lt;/code&gt; fulfills.
The &lt;code&gt;timeout&lt;/code&gt; function itself still returns immediately. But &lt;code&gt;await&lt;/code&gt; makes &lt;code&gt;main&lt;/code&gt; wait for the timer callback to call &lt;code&gt;resolve()&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;figcaption&gt;timeout.js&lt;/figcaption&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;&quot;use strict&quot;

function timeout(ms) {
  console.log(&quot;timeout start&quot;)

  return new Promise((resolve) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      console.log(`timeout cb fired after ${ms} ms`)
      resolve()
    }, ms)
  })
}

async function main() {
  console.log(&quot;main start&quot;)
  await timeout(5e3)
  console.log(&quot;main end&quot;)
}

exports.handler = main&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;When we run our code with this change, all is well now.&lt;/p&gt;
&lt;figure class=&quot;post-image&quot;&gt;&lt;img alt=&quot;Lambda invocation waits for the awaited timeout callback&quot; sizes=&quot;min(calc(70ch - 80px), calc(100vw - 2.5rem - 80px))&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2600&quot; height=&quot;1599&quot; src=&quot;https://dans.land/assets/3.D-RioW_E_1x7Pxg.webp&quot; srcset=&quot;https://dans.land/assets/3.D-RioW_E_ZovtPy.webp 320w, https://dans.land/assets/3.D-RioW_E_AoFbf.webp 480w, https://dans.land/assets/3.D-RioW_E_F2O3s.webp 672w, https://dans.land/assets/3.D-RioW_E_ZdTqtp.webp 960w, https://dans.land/assets/3.D-RioW_E_1kxGm2.webp 1344w, https://dans.land/assets/3.D-RioW_E_1x7Pxg.webp 2600w&quot;&gt;&lt;figcaption aria-hidden=&quot;true&quot;&gt;Lambda invocation waits for the awaited timeout callback&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;macrotasks-and-microtasks&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#macrotasks-and-microtasks&quot;&gt;Macrotasks and microtasks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I intentionally left out some details about the task queue.
There are actually several queues.
For example, one for macrotasks (e.g. &lt;code&gt;setTimeout&lt;/code&gt; callbacks) and one for microtasks (e.g. &lt;code&gt;Promise&lt;/code&gt; handlers like &lt;code&gt;.then()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Node.js processes the microtask queue before moving on to the next event loop callback.
While these microtasks are processed they can enqueue more microtasks, &lt;strong&gt;which will also be processed before moving on&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For more information see this &lt;a href=&quot;https://blog.risingstack.com/node-js-at-scale-understanding-node-js-event-loop/&quot;&gt;RisingStack article&lt;/a&gt; where they go into more detail.&lt;/p&gt;
&lt;blockquote class=&quot;callout callout--note&quot; data-callout=&quot;note&quot;&gt;
&lt;p class=&quot;callout-title&quot;&gt;&lt;span aria-hidden=&quot;true&quot; class=&quot;callout-icon&quot;&gt;※&lt;/span&gt;Note&lt;/p&gt;
&lt;p&gt;This page was originally published on &lt;a href=&quot;https://medium.com/radient-tech-blog/aws-lambda-and-the-node-js-event-loop-864e48fba49&quot;&gt;Medium&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 id=&quot;footnote-label&quot;&gt;&lt;a class=&quot;heading-link&quot; href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;Adapted from &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model#job_queue_and_event_loop&quot;&gt;MDN&lt;/a&gt;. &lt;a href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;user-content-fn-2&quot;&gt;
&lt;p&gt;See the AWS &lt;a href=&quot;https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html#nodejs-handler-patterns&quot;&gt;handler&lt;/a&gt; docs for the callback and async handler patterns. &lt;a href=&quot;https://dans.land/garden/lambda/nodejs-event-loop#user-content-fnref-2&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 2&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded></item></channel></rss>