Files
seaweedfs/seaweed-volume/src
Chris Lu e82789ea4b rust(volume): strip grpc-port suffix from master URL before HTTP lookup (#9276)
* rust(volume): strip grpc-port suffix from master URL before HTTP lookup

The volume server stores `master_url` in SeaweedFS's canonical
`host:httpPort.grpcPort` form (e.g. `node-a:5300.5310`). When
`lookup_volume` builds the master `/dir/lookup` URL, the appended
gRPC port turns the URL into `http://node-a:5300.5310/...`, which
reqwest rejects with "builder error". Every replicated write,
batch-delete lookup, and proxy/redirect read then fails.

Mirror Go's `pb.ServerAddress.ToHttpAddress()` with a new
`to_http_address` helper and apply it inside `lookup_volume`, the
single funnel for all three HTTP master lookups in the Rust volume
server. Other consumers of `master_url` already go through gRPC and
use `to_grpc_address` / `parse_grpc_address`.

Includes a regression test that mocks a master HTTP server and calls
`lookup_volume` with a `host:port.grpcPort` master URL — without the
fix it reproduces the exact "lookup request failed: builder error"
from issue #9274.

Fixes #9274

* rust(volume): only strip dotted suffix from address when both ports are numeric

Previously `to_http_address` rewrote any `host:foo.bar` to `host:foo`,
which would silently drop the suffix on malformed config (e.g. a
hostname like `host:abc.def` or `host:99999.19333`). Validate that
both halves of the dotted suffix parse as `u16` before stripping —
mirrors the validation that `to_grpc_address` already does in the
inverse direction. Also slice the input directly instead of going
through `format!`, since the result is just a prefix of `addr`.

Adds a test that asserts non-numeric / out-of-range dotted suffixes
are preserved unchanged.

* rust(volume): strip grpc-port suffix from peer URLs in replicate / proxy / redirect

In normal operation the master returns `Location.url` as plain
`host:port` and the gRPC port arrives in a separate field. But the
volume server already has defensive logic (`grpc_address_for_location`)
for the `host:httpPort.grpcPort` form on those same URLs, which
implies a code path where peer URLs do carry the suffix.

Apply `to_http_address` to `loc.url` / `target.url` before building
HTTP URLs in `do_replicated_request`, `proxy_request`, and
`redirect_request` to keep replicate-write, proxy-read, and redirect
paths from hitting the same `lookup request failed: builder error`
mode that #9274 documented for master lookups.

Adds a unit test exercising `redirect_request` with a `.grpcPort`
suffix on `target.url`.

* rust(volume): return Cow<str> from to_http_address to skip allocation on the no-suffix path

Most addresses pass through `to_http_address` unchanged (master and
peer URLs are normally plain `host:port`), so the previous String
return type allocated on every call for nothing. Switch to `Cow<str>`:
the common pass-through borrows from the input, and only the rewrite
branch allocates. Call sites use the result via `format!`/`Display`,
which both work transparently with `Cow<str>`.

Adds a test asserting the variant is Borrowed in the no-rewrite cases
and Owned only when the suffix is stripped.

* rust(volume): cover bracketed IPv6 literals in to_http_address tests

The current implementation already handles IPv6 correctly because
`rfind(':')` lands on the colon after the closing bracket, leaving
the dotted suffix logic unchanged. Add explicit test coverage so the
behavior is locked down for dual-stack deployments — both the strip
case (`[::1]:9333.19333` -> `[::1]:9333`) and the various passthrough
cases (no suffix, non-numeric, out-of-range, trailing colon).

* rust(volume): parse both ports in one tuple pattern match in to_http_address

Combine the two `is_ok()` checks into a single `if let (Ok(_), Ok(_))`
tuple match — equivalent semantics, slightly tighter expression of
intent. No behavior change.
2026-04-29 00:51:10 -07:00
..