Skip to content

fix(core): route host-style storage addressing on emulator hostnames - #268

Merged
hectorvent merged 2 commits into
floci-io:mainfrom
cmcconomyfwig:fix/host-style-account-routing
Sep 7, 2026
Merged

fix(core): route host-style storage addressing on emulator hostnames#268
hectorvent merged 2 commits into
floci-io:mainfrom
cmcconomyfwig:fix/host-style-account-routing

Conversation

@cmcconomyfwig

Copy link
Copy Markdown
Contributor

Closes #267

Problem

Real Azure addresses storage host-style: the account is the first host label and the container is the first path segment. AzureRoutingFilter resolved the account only from the path — host-based routing existed solely for the production suffixes (.blob.core.windows.net, …) — so a host-style request against an emulator hostname had its container consumed as the account and Create Container answered 501 NotImplemented:

$ curl -X PUT 'http://localhost:4577/awjh2?restype=container' \
       -H 'Host: devstoreaccount1.blob.localhost:4577'
HTTP/1.1 501 Not Implemented

The .NET Azure Functions host emits exactly this shape for AzureWebJobsStorage, so the azure-webjobs-hosts bootstrap failed and Durable/timer triggers never started — the remaining blocker after #182/#183 (details and wire traces in #267).

Fix

  • New routing stage routeByHostServiceMarker (immediately after the production-suffix stage): a service-marker table is derived from the registered host suffixes' first labels (blob, dfs, queue, table, vault, communication, servicebus), and {account}.{marker}.{any-host} routes exactly like {account}.{marker}.core.windows.net. So devstoreaccount1.blob.localhost and devstoreaccount1.blob.floci-az resolve to account devstoreaccount1, service blob, with the full path as the resource.
  • TableServiceHandler now registers .table.core.windows.net — table previously had no host route at all, so it gains both the production suffix and the marker, completing the blob/queue/table trio AzureWebJobsStorage needs.
  • Path-style callers are unaffected: a host without a known service marker as its second label (including any FQDN an emulator is served at, e.g. floci-az.mycorp.local) falls through to the account-suffix terminal unchanged. Duplicate markers across service types fail fast at startup, same as the existing duplicate-suffix checks.

With this, the host-style connection string works end to end (with docker network aliases or a wildcard DNS entry for the three names):

DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8…;
BlobEndpoint=http://devstoreaccount1.blob.floci-az:4577;
QueueEndpoint=http://devstoreaccount1.queue.floci-az:4577;
TableEndpoint=http://devstoreaccount1.table.floci-az:4577;

Host-style and path-style share the account namespace: a blob written through devstoreaccount1.blob.… is readable at /devstoreaccount1/… and vice versa.

Tests

New routing tests (all watched fail with the exact #267 symptom — 501 / misrouted list — before the fix):

  • the verbatim issue reproduction (host-style Create Container → 201)
  • host-style write / path-style read namespace sharing
  • queue and table marker hosts
  • .table.core.windows.net production-suffix routing
  • a dotted, marker-less Host stays path-style (regression guard for FQDN deployments)

RoutingTableAssemblyTest's golden host table updated for the new table suffix. Full unit suite green.

Docs follow-up: the README's connection-string section only documents path-style; happy to add the host-style shape in a separate docs PR if wanted.

Real Azure addresses storage host-style: the account is the first host
label and the container is the first path segment. AzureRoutingFilter
resolved the account only from the path — host-based routing existed
solely for the production suffixes like .blob.core.windows.net — so a
host-style request against an emulator hostname had its container
consumed as the account and Create Container answered 501. The .NET
Azure Functions host emits exactly that shape for AzureWebJobsStorage,
so its azure-webjobs-hosts bootstrap failed and Durable/timer triggers
never started.

Derive a service-marker table from the registered host suffixes' first
labels (blob, dfs, queue, table, vault, communication, servicebus) and
add a routing stage that resolves {account}.{marker}.{any-host} exactly
like {account}.{marker}.core.windows.net — devstoreaccount1.blob.localhost
and devstoreaccount1.blob.floci-az now route to blob with account
devstoreaccount1 and the full path as the resource. Path-style callers
are unaffected: hosts without a service marker as their second label
fall through to the account-suffix terminal unchanged, and duplicate
markers across service types fail fast at startup. TableServiceHandler
now registers .table.core.windows.net, so table gains both the
production suffix and the marker, completing the blob/queue/table trio
AzureWebJobsStorage needs.

Routing tests pin the issue reproduction (host-style Create Container),
host/path namespace sharing, queue and table marker hosts, the new
table suffix, and that a dotted marker-less host stays path-style.

Closes floci-io#267
@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown

Greptile Summary

The PR fixes host-style storage routing on emulator hostnames and completes Table Storage host routing.

  • Derives service markers from registered host suffixes and routes {account}.{service}.{emulator-host} before path-style fallback.
  • Normalizes hostnames case-insensitively, resolving the previously reported mixed-case marker issue.
  • Registers the Table Storage production suffix and adds coverage for Blob, Queue, Table, namespace sharing, mixed-case hosts, and marker-less FQDNs.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/main/java/io/floci/az/core/AzureRoutingFilter.java Adds case-insensitive host service-marker routing while preserving production-suffix priority and path-style fallback.
src/main/java/io/floci/az/services/table/TableServiceHandler.java Registers the standard Table Storage host suffix so both production-style and emulator-marker routing reach the table handler.
src/test/java/io/floci/az/core/AzureRoutingFilterTest.java Covers host-style Blob, Queue, and Table routing, mixed-case host normalization, shared account state, and marker-less FQDN fallback.
src/test/java/io/floci/az/core/RoutingTableAssemblyTest.java Updates the routing-table golden set to require the newly registered Table Storage host suffix.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming storage request] --> B[Normalize Host and remove port]
    B --> C{Production host suffix matches?}
    C -- Yes --> D[Extract account and dispatch service]
    C -- No --> E{Second host label is registered service marker?}
    E -- Yes --> F[Extract first label as account]
    F --> D
    E -- No --> G[Continue path-based routing]
    G --> H[Account-suffix or storage fallback]
Loading

Reviews (2): Last reviewed commit: "fix(core): compare hostnames case-insens..." | Re-trigger Greptile

Comment thread src/main/java/io/floci/az/core/AzureRoutingFilter.java
Hostnames are case-insensitive (RFC 4343), but both host-routing stages
compared the raw Host header: the marker lookup missed
devstoreaccount1.BLOB.localhost and fell back to path-style — recreating
the bootstrap failure — and the pre-existing production-suffix stage
equally missed acct.BLOB.core.windows.net. Lowercase the host once at
capture (hostWithoutPort, alongside the port strip) so every host
comparison sees the normalized form and the account label lands in the
lowercase namespace Azure account names use.

A mixed-case host-style create-container test pins the behavior,
including visibility of the container in the lowercase path-style
namespace.
@hectorvent hectorvent added bug Something isn't working blob Azure Blob Storage queue Azure Queue Storage table Azure Table Storage labels Sep 7, 2026

@hectorvent hectorvent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this is a clean fix and the reasoning in the description is the best part. I checked the design choice against Azurite, which treats any dotted hostname as production style with no marker required (blobStorageContext.middleware.ts#L271). Requiring a known service marker is stricter than that on purpose, and you are right to be: an emulator served at an FQDN would otherwise have its first label eaten as the account, which is exactly the footgun Azurite ships a flag to disable. The FQDN regression test pins that decision.

The case-insensitivity follow-up landed where I would have asked for it: normalising once in hostWithoutPort covers the production-suffix stage as well as the new marker stage, and the mixed-case test proves the account lands in the lowercase namespace rather than in one that matches nothing.

No blockers from my side.

@hectorvent
hectorvent merged commit b2d64d0 into floci-io:main Sep 7, 2026
12 checks passed
@cmcconomyfwig
cmcconomyfwig deleted the fix/host-style-account-routing branch September 8, 2026 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blob Azure Blob Storage bug Something isn't working queue Azure Queue Storage table Azure Table Storage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Account resolved only from the path; Host-header (host-style) addressing returns 501 — blocks Functions AzureWebJobsStorage

2 participants