Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9d578d6
Add file server filter
ravenblackx Jan 30, 2026
36edcd2
Improve coverage, fix docgen
ravenblackx Feb 2, 2026
ff9ba4f
Accommodate rename of com_github_cncf_xds
ravenblackx Feb 2, 2026
2b32c67
Merge branch 'main' into file_server
ravenblackx Feb 2, 2026
ff97678
Move abslStatusToHttpStatus into its own library, and add coverage
ravenblackx Feb 2, 2026
f46d7dd
Add one more line of test coverage!
ravenblackx Feb 2, 2026
908bee7
Remove unused usings
ravenblackx Feb 2, 2026
16c41f0
Remove deps that are inherited anyway, to avoid conflicting with #43265
ravenblackx Feb 2, 2026
d0749b1
try_file->default_file, directory_list->list, drop oneof
ravenblackx Feb 3, 2026
edbbdba
Add virtual destructor to FileStreamerClient
ravenblackx Feb 3, 2026
9b9b443
DirectoryList->List too
ravenblackx Feb 3, 2026
0da1383
Format
ravenblackx Feb 3, 2026
006ed2d
Double backticks
ravenblackx Feb 3, 2026
52c4555
path_prefix->request_path_prefix, filesystem_prefix->file_path_prefix
ravenblackx Feb 5, 2026
81ae8dc
Format
ravenblackx Feb 6, 2026
f0481d7
Address copilot comments
ravenblackx Feb 9, 2026
fa7ea2a
Hide the unused message type from docgen
ravenblackx Feb 10, 2026
e59c932
Merge branch 'main' into file_server
ravenblackx Feb 10, 2026
f80dfe8
Address comments
ravenblackx Feb 23, 2026
d2ef56c
Add wbpcode as CODEOWNER
ravenblackx Feb 23, 2026
5cb575b
Make manager_config mandatory
ravenblackx Feb 23, 2026
66e9e62
Fuzz tester limitation
ravenblackx Feb 23, 2026
834c57a
Ensure config destruction happens in the same thread
ravenblackx Feb 23, 2026
eed2662
The right delete!
ravenblackx Feb 23, 2026
2ec9d35
Merge branch 'main' into file_server
ravenblackx Feb 24, 2026
2da03e3
Merge branch 'main' into file_server
ravenblackx Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ extensions/upstreams/tcp @ggreenway @mattklein123
/*/extensions/config/validators/minimum_clusters @adisuissa @yanavlasov
# File system based extensions
/*/extensions/common/async_files @mattklein123 @ravenblackx
/*/extensions/filters/http/file_server @ravenblackx @wbpcode @phlax
/*/extensions/filters/http/file_system_buffer @mattklein123 @ravenblackx
/*/extensions/http/cache/file_system_http_cache @ggreenway @UNOWNED
/*/extensions/http/cache_v2/file_system_http_cache @ggreenway @ravenblackx
Expand Down
1 change: 1 addition & 0 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ proto_library(
"//envoy/extensions/filters/http/ext_authz/v3:pkg",
"//envoy/extensions/filters/http/ext_proc/v3:pkg",
"//envoy/extensions/filters/http/fault/v3:pkg",
"//envoy/extensions/filters/http/file_server/v3:pkg",
"//envoy/extensions/filters/http/file_system_buffer/v3:pkg",
"//envoy/extensions/filters/http/gcp_authn/v3:pkg",
"//envoy/extensions/filters/http/geoip/v3:pkg",
Expand Down
13 changes: 13 additions & 0 deletions api/envoy/extensions/filters/http/file_server/v3/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.

load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")

licenses(["notice"]) # Apache 2

api_proto_package(
deps = [
"//envoy/extensions/common/async_files/v3:pkg",
"@xds//udpa/annotations:pkg",
"@xds//xds/annotations/v3:pkg",
],
)
85 changes: 85 additions & 0 deletions api/envoy/extensions/filters/http/file_server/v3/file_server.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
syntax = "proto3";

package envoy.extensions.filters.http.file_server.v3;

import "envoy/extensions/common/async_files/v3/async_file_manager.proto";

import "xds/annotations/v3/status.proto";

import "udpa/annotations/status.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.extensions.filters.http.file_server.v3";
option java_outer_classname = "FileServerProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/file_server/v3;file_serverv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;
option (xds.annotations.v3.file_status).work_in_progress = true;

// [#protodoc-title: FileServerConfig]
// [#extension: envoy.filters.http.file_server]

// A :ref:`file server <config_http_filters_file_server>` filter configuration.
// [#next-free-field: 6]
message FileServerConfig {
message PathMapping {
// If no ``request_path_prefix`` is matched, the filter does not intercept a request.
//
// If a ``request_path_prefix`` is matched, that prefix is removed from the request
// and replaced with ``file_path_prefix`` to form a filesystem path for
// the request.
//
// Prefix ``/`` will match all GET requests.
string request_path_prefix = 1 [(validate.rules).string = {min_len: 1}];

// Replaces a matched ``request_path_prefix`` to form a filesystem path for a
// request. May be relative to the working directory of the envoy execution,
// or an absolute path.
string file_path_prefix = 2 [(validate.rules).string = {min_len: 1}];
}

message DirectoryBehavior {
// [#not-implemented-hide:] Directory operations currently have no async implementation.
message List {
}
Comment on lines +42 to +44
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: as a fresh new extension, I think any field that not implemented could be removed first because:

  1. We may find no one want it in the future.
  2. We may have a better API design in the future.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We want it - the catch is just that it requires implementing async directory operations first which is going to be additional PRs that I didn't want to roll up into this already large PR.

And I wanted the API review before going for that implementation. :)


// Attempts to serve the given file within the directory, e.g. ``index.html``.
// Precisely one of ``default_file`` and ``list`` must be set per ``DirectoryBehavior``.
string default_file = 1 [(validate.rules).string = {pattern: "^[^/]*$"}];

// Responds with an html formatted list of the files and subdirectories in the directory.
// Precisely one of ``default_file`` and ``list`` must be set per ``DirectoryBehavior``.
// [#not-implemented-hide:] Directory operations currently have no async implementation.
List list = 2;
Comment thread
ravenblackx marked this conversation as resolved.
}

// A configuration for the AsyncFileManager to be used to read from the filesystem.
common.async_files.v3.AsyncFileManagerConfig manager_config = 1
[(validate.rules).message = {required: true}];

// The longest matching path_mapping takes precedence.
repeated PathMapping path_mappings = 2;

// A map from filename suffix to content type header.
// e.g. {"txt": "text/plain"}
//
// File suffixes may not contain ``.`` as the filename suffix after
// the last ``.`` is used to perform an O(1) lookup against the keys.
//
// An empty string suffix will only match files ending with a ``.``.
//
// Files with no suffix (e.g. ``README``) can be matched as the full string.
map<string, string> content_types = 3;

// If ``content_types`` does not contain a match for a file suffix,
// ``default_content_type`` is used.
//
// If there is no match and ``default_content_type`` is empty, the
// ``content-type`` header will be omitted from the response.
string default_content_type = 4;

// If the requested path refers to a directory, the given behaviors are
// tried in order until one succeeds. If the end of the list is reached
// with no success, the result is a 403 Forbidden.
repeated DirectoryBehavior directory_behaviors = 5;
}
1 change: 1 addition & 0 deletions api/versioning/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ proto_library(
"//envoy/extensions/filters/http/ext_authz/v3:pkg",
"//envoy/extensions/filters/http/ext_proc/v3:pkg",
"//envoy/extensions/filters/http/fault/v3:pkg",
"//envoy/extensions/filters/http/file_server/v3:pkg",
"//envoy/extensions/filters/http/file_system_buffer/v3:pkg",
"//envoy/extensions/filters/http/gcp_authn/v3:pkg",
"//envoy/extensions/filters/http/geoip/v3:pkg",
Expand Down
1 change: 1 addition & 0 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ PPC_SKIP_TARGETS = ["envoy.string_matcher.lua", "envoy.filters.http.lua", "envoy
WINDOWS_SKIP_TARGETS = [
"envoy.extensions.http.cache.file_system_http_cache",
"envoy.extensions.http.cache_v2.file_system_http_cache",
"envoy.filters.http.file_server",
"envoy.filters.http.file_system_buffer",
"envoy.filters.http.language",
"envoy.filters.http.sxg",
Expand Down
20 changes: 20 additions & 0 deletions docs/root/configuration/http/http_filters/file_server_filter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.. _config_http_filters_file_server:

File Server
===========

The file server filter can be used to respond with the contents of a file from the filesystem.

The ``content-length`` header will be the size of the file.

The ``content-type`` header will be set based on filename suffix and filter configuration.

.. note::

This filter is not yet supported on Windows.

Configuration
-------------

* This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.file_server.v3.FileServerConfig``.
* :ref:`v3 API reference <envoy_v3_api_msg_extensions.filters.http.file_server.v3.FileServerConfig>`
1 change: 1 addition & 0 deletions docs/root/configuration/http/http_filters/http_filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ HTTP filters
ext_authz_filter
ext_proc_filter
fault_filter
file_server_filter
file_system_buffer_filter
gcp_authn_filter
geoip_filter
Expand Down
1 change: 1 addition & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ EXTENSIONS = {
"envoy.filters.http.ext_authz": "//source/extensions/filters/http/ext_authz:config",
"envoy.filters.http.ext_proc": "//source/extensions/filters/http/ext_proc:config",
"envoy.filters.http.fault": "//source/extensions/filters/http/fault:config",
"envoy.filters.http.file_server": "//source/extensions/filters/http/file_server:config",
"envoy.filters.http.file_system_buffer": "//source/extensions/filters/http/file_system_buffer:config",
"envoy.filters.http.gcp_authn": "//source/extensions/filters/http/gcp_authn:config",
"envoy.filters.http.geoip": "//source/extensions/filters/http/geoip:config",
Expand Down
7 changes: 7 additions & 0 deletions source/extensions/extensions_metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,13 @@ envoy.filters.http.fault:
status: stable
type_urls:
- envoy.extensions.filters.http.fault.v3.HTTPFault
envoy.filters.http.file_server:
categories:
- envoy.filters.http
security_posture: unknown
status: wip
type_urls:
- envoy.extensions.filters.http.file_server.v3.FileServerConfig
envoy.filters.http.file_system_buffer:
categories:
- envoy.filters.http
Expand Down
56 changes: 56 additions & 0 deletions source/extensions/filters/http/file_server/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_extension",
"envoy_cc_library",
"envoy_extension_package",
)

licenses(["notice"]) # Apache 2

envoy_extension_package()

envoy_cc_library(
name = "absl_status_to_http_status",
srcs = ["absl_status_to_http_status.cc"],
hdrs = ["absl_status_to_http_status.h"],
deps = [
"//envoy/http:codes_interface",
],
)

envoy_cc_library(
name = "file_server_lib",
srcs = [
"file_streamer.cc",
"filter.cc",
"filter_config.cc",
],
hdrs = [
"file_streamer.h",
"filter.h",
"filter_config.h",
],
deps = [
":absl_status_to_http_status",
"//envoy/buffer:buffer_interface",
"//envoy/server:instance_interface",
"//source/common/common:enum_to_int",
"//source/common/common:radix_tree_lib",
"//source/common/http:codes_lib",
"//source/common/http:header_map_lib",
"//source/extensions/common/async_files",
"//source/extensions/filters/http/common:pass_through_filter_lib",
"@envoy_api//envoy/extensions/filters/http/file_server/v3:pkg_cc_proto",
],
)

envoy_cc_extension(
name = "config",
srcs = ["config.cc"],
hdrs = ["config.h"],
deps = [
":file_server_lib",
"//source/extensions/filters/http/common:factory_base_lib",
"@envoy_api//envoy/extensions/filters/http/file_server/v3:pkg_cc_proto",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include "source/extensions/filters/http/file_server/absl_status_to_http_status.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace FileServer {

Http::Code abslStatusToHttpStatus(absl::StatusCode code) {
switch (code) {
case absl::StatusCode::kOk:
return Http::Code::OK;
case absl::StatusCode::kCancelled:
return static_cast<Http::Code>(499);
case absl::StatusCode::kUnknown:
return Http::Code::InternalServerError;
case absl::StatusCode::kInvalidArgument:
return Http::Code::BadRequest;
case absl::StatusCode::kDeadlineExceeded:
return Http::Code::GatewayTimeout;
case absl::StatusCode::kNotFound:
return Http::Code::NotFound;
case absl::StatusCode::kAlreadyExists:
return Http::Code::Conflict;
case absl::StatusCode::kPermissionDenied:
return Http::Code::Forbidden;
case absl::StatusCode::kResourceExhausted:
return Http::Code::TooManyRequests;
case absl::StatusCode::kFailedPrecondition:
return Http::Code::BadRequest;
case absl::StatusCode::kAborted:
return Http::Code::Conflict;
case absl::StatusCode::kOutOfRange:
return Http::Code::RangeNotSatisfiable;
case absl::StatusCode::kUnimplemented:
return Http::Code::ServiceUnavailable;
case absl::StatusCode::kDataLoss:
return Http::Code::InternalServerError;
case absl::StatusCode::kUnauthenticated:
return Http::Code::Unauthorized;
default:
return Http::Code::InternalServerError;
}
}

} // namespace FileServer
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include "envoy/http/codes.h"

#include "absl/status/status.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace FileServer {

Http::Code abslStatusToHttpStatus(absl::StatusCode code);

} // namespace FileServer
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
Loading
Loading