Module: Otto::Core::StaticMounts

Included in:
Otto
Defined in:
lib/otto/core/static_mounts.rb

Overview

Explicit static-file registration (issue #267).

A static mount binds a URL prefix to one directory on disk:

otto.mount_static('/assets', root: 'public/assets')

Requests for GET /assets/ are then resolved against public/assets/ using the same containment policy as the implicit public: directory (Otto::Core::FileSafety): the candidate is canonicalized with File.realpath and must land inside the mount's canonical root, be a regular readable file, and be owned by the process user or group. A mount never authorizes anything outside its own root, so several mounts can point into unrelated directories without exposing their parents or siblings.

Registration is a boot-time operation. The root is canonicalized once, when the mount is registered, and every failure mode (missing, unreadable, not a directory, escaping symlink, malformed prefix, duplicate prefix) raises ArgumentError immediately so a misconfigured application does not start. Mounts participate in configuration freezing: mount_static raises FrozenError after freeze_configuration!, and the mount table is an immutable, sorted snapshot that dispatch reads without any per-request mutation.

Dispatch precedence is fixed: literal routes, then static mounts (longest prefix first), then the implicit public: directory, then dynamic routes. A mount claims files, not the prefix: when no mount root contains the requested file the request falls through to the next stage exactly as an unregistered path would. See Otto::Core::Router.

Defined Under Namespace

Classes: StaticMount

Instance Method Summary collapse

Instance Method Details

#mount_static(prefix, root:) ⇒ StaticMount

Serve the files under root at URLs beneath prefix.

Parameters:

  • prefix (String)

    URL prefix starting with '/'. A trailing slash is ignored; '/' mounts the root at the top level.

  • root (String)

    directory path; relative paths resolve against the process working directory and are canonicalized immediately.

Returns:

Raises:

  • (ArgumentError)

    on a malformed prefix, a duplicate prefix, or a root that is missing, unreadable, not a directory, not owned by the process user or group, or that cannot be canonicalized.

  • (FrozenError)

    after configuration freezing



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/otto/core/static_mounts.rb', line 83

def mount_static(prefix, root:)
  ensure_not_frozen!

  clean_prefix = normalize_mount_prefix(prefix)
  if @static_mounts.any? { |mount| mount.prefix == clean_prefix }
    raise ArgumentError,
          "Static mount prefix #{display_mount_prefix(clean_prefix).inspect} is already registered"
  end

  canonical_root = canonicalize_mount_root(clean_prefix, root)
  mount = StaticMount.new(clean_prefix, canonical_root, Rack::Files.new(canonical_root).freeze).freeze

  # Longest prefix first so an overlay ('/assets/vendor') is consulted
  # before the mount that contains it ('/assets'). Ties cannot happen:
  # prefixes are unique. Rebuild rather than mutate so in-flight readers
  # keep their snapshot.
  @static_mounts = (@static_mounts + [mount]).sort_by { |m| -m.prefix.length }.freeze

  Otto.structured_log(:debug, 'Static mount registered',
    { prefix: mount.display_prefix, root: mount.root })
  mount
end

#static_mountsArray<StaticMount>

Registered mounts, longest prefix first. Frozen snapshot; a new array replaces it on every registration so readers never observe a partial update.

Returns:



68
69
70
# File 'lib/otto/core/static_mounts.rb', line 68

def static_mounts
  @static_mounts
end