EzyHTTP Serves Static Files
Updated at 1783611087000EzyHTTP can serve static files such as HTML, CSS, JavaScript, images, fonts, WebAssembly files, or other downloadable assets. The feature is disabled by default, so an application only exposes static resources when you explicitly enable it.
Static file serving in EzyHTTP is designed around two ideas:
- only files discovered from configured resource locations are mapped to public URLs;
- file content is streamed asynchronously so long downloads do not keep normal request handler threads busy.
Why Static Files Are Served Asynchronously
Serving a normal JSON response is usually quick: the controller returns an object, EzyHTTP serializes it, writes the response, and the request thread can handle the next request.
Static files are different. A client may download a large image, video, archive, or WebAssembly bundle. If every download keeps one request handling thread blocked until the whole file is written, a small number of slow clients can occupy the server's worker threads for a long time.
For example, imagine the server has 8 request handler threads and one client starts 8 large downloads at the same time. In a fully blocking model, those 8 threads can all be busy writing file data. Other clients may have to wait even if their requests are small.
EzyHTTP avoids that by returning an asynchronous response for static resources. The request is accepted, the servlet async context is used, and file streaming is delegated to a resource download manager. That manager keeps a queue of download entries and a dedicated worker pool. Each worker reads a chunk from an input stream, writes that chunk to the response output stream, and puts unfinished downloads back into the queue.
In simplified form, the download loop behaves like this:
take a download task from the queue
read a small chunk from the file input stream
write the chunk to the HTTP response output stream
if the file is finished:
close the input stream
complete the async response
else:
put the task back into the queue
This keeps static resource transfer separate from normal controller execution. It also gives multiple downloads a chance to make progress instead of letting one large response monopolize a worker for the whole transfer.
Enable Static Resources
Static resources are disabled by default. To enable them in
application.properties, add:resources.enable=true
The same setting in
application.yaml is:resources: enable: true
When
resources.enable is true and no custom location is configured, EzyHTTP looks for resources under:static
In a Maven or Gradle project this usually means:
src/main/resources/static
For example:
src/main/resources/static/index.html src/main/resources/static/css/style.css src/main/resources/static/js/main.js src/main/resources/static/images/logo.png
These files are exposed as:
/index.html /css/style.css /js/main.js /images/logo.png
The configured resource location itself is not part of the public URL. It is only the root used to discover files.
Configure Resource Locations
Use
resources.location when you only need one location:resources.location=public
With this configuration:
src/main/resources/public/css/style.css
is served as:
/css/style.css
Use
resources.locations when you want to scan multiple locations:resources.locations=static,public,assets
In YAML:
resources:
enable: true
locations:
- static
- public
- assets
If
resources.locations is present, it takes priority over resources.location.EzyHTTP can discover resources from normal folders during development and from classpath or JAR resources after the application is packaged. That means the same configuration works both when running from an IDE and when running from a built application artifact.
How URLs Are Created
For each discovered file, EzyHTTP removes the configured location prefix and uses the remaining path as the request URI.
Example with
resources.location=static:| File path under the resource location | Public URI |
|---|---|
static/index.html | /index.html |
static/css/style.css | /css/style.css |
static/js/main.js | /js/main.js |
static/docs/getting-started.html | /docs/getting-started.html |
Only real files are registered. Directories are used for traversal, but they are not mapped as downloadable resources.
By default, EzyHTTP accepts file paths that look like normal static asset paths: letters, digits, underscores, hyphens, dots, slashes, and backslashes, ending with a file extension. This keeps the mapping focused on files such as
.html, .css, .js, .png, .jpg, .svg, .wasm, .gz, and similar assets.Filter Files With A Pattern
If you want to expose only a subset of files, configure
resources.pattern with a regular expression.Example: serve only CSS and JavaScript files:
resources.pattern=.*.(css|js)
Example: serve only files inside
static/public:resources.pattern=static/public/.*..+
The pattern is matched against the resource path discovered from the configured location. EzyHTTP also normalizes Windows-style separators to
/ when checking the pattern, so the same rule can work across operating systems.Request Handling Flow
When static resources are enabled, EzyHTTP builds a GET handler for each discovered file during application startup.
The runtime flow is:
flowchart TD
A[Application starts] --> B{resources.enable?}
B -- false --> C[No static resource handlers]
B -- true --> D[Resolve resource locations]
D --> E[Scan folders, classpath resources, or JAR entries]
E --> F[Create one GET mapping per file]
F --> G[Client requests a static URI]
G --> H[Open resource input stream]
H --> I[Set content type and response headers]
I --> J[Stream file through async download manager]
J --> K[Complete async response]
If a mapped file cannot be opened at request time, EzyHTTP returns a not found response. If an unexpected streaming error happens after the response starts, the async response is completed and the status is set to an internal server error.
Content Type And Content Encoding
EzyHTTP chooses the response content type from the file extension.
Typical examples:
| File | Expected response type |
|---|---|
index.html | text/html |
style.css | text/css |
main.js | JavaScript content type |
image.png | PNG image content type |
file.gz | gzip content type |
There is also special handling for assets whose extension carries both the real file type and the compression extension. For example:
resource.wasm.gz
is treated as a WebAssembly resource with gzip content encoding. This lets a client receive the correct actual content type while still knowing that the transferred bytes are gzip encoded.
Range Requests
EzyHTTP supports HTTP byte range downloads for static resources. If the request includes a
Range header, the server reads only the requested byte range and responds with partial content.Example request header:
Range: bytes=0-1023
The response includes headers such as:
Accept-Ranges: bytes Content-Range: bytes 0-1023/FILE_SIZE Content-Length: 1024
This is useful for large files, resumable downloads, media playback, and clients that only need part of a resource.
If the range has a start but no explicit end, EzyHTTP reads a bounded chunk from that start position instead of blindly streaming the rest of the file. The current maximum chunk length for this open-ended range path is 2 MB.
Example:
Range: bytes=1048576-
The server starts at byte
1048576 and returns at most one bounded chunk, adjusted if the file ends earlier.Download Manager Settings
Static file downloads use the resource download manager. You can tune it with these properties:
| Property | Default | Meaning |
|---|---|---|
resources.download.capacity | 100000 | Maximum number of queued download tasks. |
resources.download.thread_pool_size | available processors | Number of worker threads used for static download streaming. |
resources.download.buffer_size | 1024 | Number of bytes each worker attempts to read and write per step. |
Example:
resources.enable=true resources.download.capacity=20000 resources.download.thread_pool_size=8 resources.download.buffer_size=4096
Larger buffers can reduce loop overhead for big files, while smaller buffers can make scheduling between many concurrent downloads more granular. The best value depends on your file sizes, network behavior, and server resources.
Async Timeout
Static resource handlers use servlet asynchronous processing. EzyHTTP also supports a general async timeout setting:
async.default_timeout=10000
In YAML:
async: default_timeout: 10000
This value is in milliseconds. A value of
0 means EzyHTTP does not configure a default timeout itself.Security Notes
EzyHTTP does not expose arbitrary filesystem paths. It scans the configured resource locations at startup and registers handlers only for files that are discovered and accepted by the file path pattern.
That means a request cannot simply walk to any file outside the configured static locations. For example, if only
static is configured, a request URI such as /../../secret.txt is not a registered resource mapping.For production applications, it is still a good practice to:
- put only public assets inside static resource folders;
- use
resources.patternwhen only part of a folder should be public; - avoid mixing private files and public files in the same resource location;
- serve very large or high-traffic assets from a CDN or object storage when that better fits your deployment.
Complete Example
Project structure:
src/main/resources/static/index.html src/main/resources/static/css/style.css src/main/resources/static/js/main.js src/main/resources/static/wasm/app.wasm.gz
Configuration:
resources.enable=true resources.location=static resources.download.thread_pool_size=4 resources.download.buffer_size=4096
Generated GET mappings:
GET /index.html GET /css/style.css GET /js/main.js GET /wasm/app.wasm.gz
When a browser requests
/css/style.css, EzyHTTP opens the corresponding resource, sets the response content type from the file extension, streams the file through the async download manager, and completes the async response after the last bytes are written.Summary
Static file serving in EzyHTTP is opt-in. Once enabled, the framework scans configured resource locations, turns discovered files into GET mappings, and streams file content asynchronously through a dedicated download manager.
This gives you a simple way to ship small and medium static assets with your application while keeping normal request handling threads available for controllers. For large public traffic, the same design can still be combined with external caching, CDN, or object storage depending on how your application is deployed.