API mock (apimock-rs) Documentation

A developer-friendly, robust and functional HTTP mock server built in Rust.
While this is a CLI tool, a featherlight GUI wrapper named API mokka will be also available. (development in progress)
Who is this for?
- Developers who want to quickly mock APIs without heavy setup.
- Beginners who benefit from minimal configuration.
- Advanced users needing logic-based response behavior.
Quick Start
npm install -D apimock-rs
npx apimock
For Users
Contribution
API mock (apimock-rs) Users Documentation
This guide walks through how to get started with apimock-rs
, a mock HTTP server for testing APIs. It covers basic setup, examples, frequently asked questions, deep configuration.
🏞️ Concept Overview
API mock (apimock-rs) is a developer-friendly, lightweight and functional HTTP mock server built in Rust. It provides file-based mechanism for mocking RESTful APIs using static JSON responses. Optionally, dynamic routing is supported: rule-based matching via toml
and scripting via rhai
.
Key Features
- 👟 Zero-config start – Just point to a directory (folder) with JSON files and run.
- 🍬 Simple setup – Usable via a single CLI command, no compilation needed via npm-distributed binary.
- 🧳 Static routing – File-based simple responses. Uses directory paths and
.json
-like files to simulate API endpoints. - 🎒 Dynamic routing with matching – Supports conditional responses with rule-based mechanism and scripting.
- 🍨 High-performance – Effortless speed and minimal presence. Built with async Rust using
tokio
andhyper
.
Getting started
It only takes a few steps to get your mock server up and running !
System Requirements
- Node.js and npm (stable) only
- (Optional) Without them, natively built executable is also available
Installation
If not installed yet:
npm install -D apimock-rs
Note that the package name is apimock-rs
(ends with -rs
) and its command name below is apimock
.
Minimal Configuration
Nothing. Just JSON and go !
Running the Server
npx apimock
Besides, if you use natively built executable, run ./apimock
instead.
Test it
From another terminal (as web client), access the server:
curl -i http://localhost:3001/
Expected response: HTTP Status Code 404 (NOT FOUND)
This is correct, as no .json
file exists on the server yet. Now, let's prepare for the next test. In the server terminal, run:
echo '{"hello": "world"}' > greetings.json
npx apimock
Then, access it again with:
curl http://localhost:3000/greetings
Expected response:
{
"hello": "world"
}
All set 😺 ?
File-based routing
The server uses your directory structure to automatically serve the correct response. When a request comes in, it checks your directories and files (like data.json
for /data
) to find a match for the URL. All you need to do is organize your files and directories where the server is running.
Example Directory Structure
Here's an example of how you might set up:
(root)/
└── api/
└── v1/
├── status.json
├── user/
│ ├── 1.json
│ └── 2.json5
└── users.csv
- A request to
/api/v1/status
returnsstatus.json
. /api/v1/user/1
returns the content of1.json
./api/v1/user/2
returns the content of2.json5
..json5
is equivalent to.json
to the server./api/v1/users
returns the content ofusers.csv
as list.- Each of
/
,/api
and/api/v1
returns HTTP Status Code 404- unless an "index" file (e.g.,
index.json
,.json5
, or.csv
. Also.html
) is present in the respective directory.
- unless an "index" file (e.g.,
What's next ?
File-based routing is great for simple cases where your response directly maps to a URL. It gets you up and running quickly for many basic mocking needs.
Expanding Your Mock Server's Capabilities
However, you'll quickly discover some limitations. For instance, you can't create responses that depend on request headers or body data, limiting your flexibility.
When your mocking needs go beyond basic URL matching, rule-based configuration becomes incredibly powerful. This approach gives you much more control, but it requires a configuration file. Don't worry, we'll walk you through setting it up so you can harness its full potential !
Root configuration
Your first configuration files
Let's create your configuration files. You don't need to open a text editor just yet ! Instead, use the --init
argument with the npx apimock
command:
npx apimock --init
After running this, you'll see "created" printed in your terminal, and you'll find two new configuration files in your project:
apimock.toml
apimock-rule-set.toml
Running the Server with Configuration
Now that you have your configuration files, let's try running the server !
npx apimock
You'll notice a line in the terminal like: "@ rule_set #1 (./apimock-rule-set.toml)
". This indicates that the example rule set is now active and routing requests.
Moving Configuration Files
You have the flexibility to move these configuration files. For example, if you place them in a tests/
directory:
your_project/
├── tests/
│ ├── apimock.toml
│ └── apimock-rule-set.toml
└── ...
You can tell the server where to find your apimock.toml
file using either the -c
or --config
argument:
npx apimock -c tests/apimock.toml
Important: If apimock.toml
references apimock-rule-set.toml
(perhaps by default), the rule set path is interpreted relative to apimock.toml
itself, not from your current working directory. Keep this in mind when organizing your files.
You're now ready to define powerful, rule-based dynamic routing !
Rule-based routing
Defining Rules
Now, let's dive into apimock-rule-set.toml
and define some powerful rules ! Each rule checks incoming requests against specific conditions (like URL path, headers, or body content) and, if they match, sends a predefined response.
Here are examples of how you can set up different types of rules in apimock-rule-set.toml
:
Example 1: Match with Request URL Path
These examples show how to define responses based on the incoming request's URL path.
# apimock-rule-set.toml
[[rules]]
when.request.url_path = ""
respond = { text = "I'm at root." }
# apimock-rule-set.toml
[[rules]]
when.request.url_path = "home"
# Make sure to create `home.json` in a JSON format!
respond.file_path = "home.json"
Test:
curl http://localhost:3001/
# I'm at root.
curl http://localhost:3001/home
# (home.json content)
Rule-based routing: More practices
In addition to URL path, you can match with HTTP method, headers and body JSON data.
Example 2: Match with HTTP Method
# apimock-rule-set.toml
[[rules]]
when.request.method = "GET"
respond = { status = 403 }
[[rules]]
when.request.method = "POST"
respond = { status = 200 }
Test:
curl -i -X GET http://localhost:3001/
# 403 Forbidden
curl -i -X POST http://localhost:3001/
# 200 OK
Example 3: Match with Headers
This example demonstrates how to match requests based on specific HTTP headers. This is useful for simulating authentication or content negotiation.
# apimock-rule-set.toml
[[rules]]
[rules.when.request.headers]
Authorization = { value = "Bearer eyJhb", op = "starts_with" }
[rules.respond]
text = "Authorized !"
Test:
curl -H "Authorization: Bearer eyJhb(...).(...).(...)" http://localhost:3001/
# Authorized !
Example 4: Match with Body JSON data by Dot-Notation JSON Path
This powerful feature lets you match requests based on specific values within the JSON body of an incoming request. You define the target key using a dot-notation path.
# apimock-rule-set.toml
[[rules]]
[rules.when.request.body.json]
"a.b.c" = { value = "d" }
[rules.respond]
text = "Body JSON matched !"
Test:
curl http://localhost:3001/ \
-H "Content-Type: application/json" \
-d '{"a":{"b":{"c":"d"}}}'
# Body JSON matched !
Understanding TOML Configuration
Your apimock.toml
and apimock-rule-set.toml
files use TOML (Tom's Obvious, Minimal Language). If you're new to TOML, don't worry ! It's designed to be human-readable and easy to learn. Here are the essentials you'll need to know:
Basic Syntax
Key-Value Pairs
The most fundamental part.
key = "value"
number_key = 123
boolean_key = true
Comments
Use the hash symbol (#
) to add comments. Anything from #
to the end of the line is ignored by the parser.
# This is a full-line comment
key = "value" # This is an end-of-line comment
Tables (Dictionaries)
TOML uses tables (similar to dictionaries or objects in other languages) to group related key-value pairs.
Inline Tables
For compact, small tables.
user = { name = "Alice", age = 30 }
Standard Tables
Defined with [table-name]
.
[listener]
ip_address = "127.0.0.1"
port = 8080
Nested Tables
You can define tables within tables using dot notation.
[rules.when.request.headers]
user = { value = "user1" }
Key Naming Flexibility
You can use hyphens (-
) in key names, not just underscores (_
). While you can enclose key names in quotes, it's often not necessary unless the key contains special characters or needs to start with a number.
api-key = "my-secret-token"
# Same as above, quotes are optional here
"api-key" = "my-secret-token"
# quotes are required here because the key contains the special character `.`
"a.b.c" = { value = "d" }
Array of Tables (Lists of Dictionaries)
This is crucial for defining your rules ! Arrays of tables are used to create a list of similar objects. Each [[table-name]]
defines a new item in the list.
[[rules]] # First rule in the list
when.request.url_path = "/home"
respond.text = "Hello, world"
[[rules]] # Second rule in the list
when.request.url_path = "/"
respond.text = "I'm at root"
For more in-depth learning
You can refer to the official TOML specification or other popular guides.
Examples
Real-World Rule-Based Routing
Now that you've got the basics down, let's explore how to use rule-based configuration to meet more realistic, real-world mocking needs. These examples will help you handle complex scenarios and make your mock server incredibly flexible.
Combining Conditions
Example
Here's an example where we combine a URL path with two HTTP headers as conditions:
[[rules]]
[rules.when.request]
url_path = "/api/check"
[rules.when.request.headers]
User = { value = "user1" }
X-Request-Id = { value = "abc123" }
[rules.respond]
text = "Matched with headers"
Important Note on Combined Conditions
These conditions are evaluated using AND logic. This means all defined conditions within a single rule must be true for the rule to match the incoming request. If even one condition isn't met, the matching fails for that particular rule.
Combining Conditions: Powerful Matching
You can combine conditions in a few ways
- Across Different Request Parts: Define multiple conditions involving the URL path, HTTP headers, and JSON body within a single rule. All specified conditions must be met for the rule to trigger.
- Multiple Conditions within Headers: Specify multiple header key-value pairs that all must be present and match.
- Multiple Conditions within Body JSON: Define multiple JSONPath conditions within the request body that all must match.
Combining conditions gives you tighter control over when a mock response is returned, enabling you to simulate complex API behaviors with precision.
Example
Here's an example where we combine a URL path with two HTTP headers and a body JSON Path as conditions:
# rule No.1 (priority)
[[rules]]
[rules.when.request]
url_path = "/api/check"
[rules.when.request.headers]
User = { value = "user1" }
X-Request-Id = { value = "abc123" }
[rules.when.request.body.json]
"a.b.c" = { value = "d" }
[rules.respond]
# Make sure to create `strictly-matched.json` in a JSON format!
file_path = "strictly-matched.json"
# rule No.2
[[rules]]
when.request.url_path = "/api/check"
when.request.headers.User = { value = "user1" }
[rules.respond]
text = "matched"
Important Note on Rule Order
The mock server uses a first-match strategy. This means it checks your rules from top to bottom in your apimock-rule-set.toml
file. The first rule that completely matches an incoming request will be applied, and no further rules will be checked. Therefore, place your most specific or highest-priority rules at the top of the file to ensure they are evaluated first.
Using Operators for Flexible Matching
So far, our rule conditions have only used exact (equal) matching. For instance, a header had to be exactly "Bearer xyz", or a path had to be exactly "/api/users".
But real-world scenarios often require more flexibility. Your mock server provides powerful operators that allow you to define various types of matching behavior:
equal
: (Default) Matches if the value is exactly the same.not_equal
: Matches if the value is not exactly the same.- Be careful ! Because it matches anything other than the specified value, it can often lead to broader matches than you intend. Consider if a more specific
equal
or other operator might be better for your use case.
- Be careful ! Because it matches anything other than the specified value, it can often lead to broader matches than you intend. Consider if a more specific
starts_with
: Matches if the value begins with a specific string.contains
: Matches if the value includes a specific string anywhere within it.wild_card
: Matches using simple wildcard patterns (*
for any sequence of characters,?
for any single character). This is incredibly versatile for dynamic paths or values.- Recommended for experienced users only. The
wild_card
operator offers immense flexibility, but it's easy to create unintended matches. Use it with caution and test your rules thoroughly to ensure they behave as expected.
- Recommended for experienced users only. The
By choosing the right operator, you can define rules that are both precise and adaptable to varying request patterns.
Example
[[rules]]
when.request.url_path = { value = "/disallowed", op = "starts_with" }
respond.status = 403 # FORBIDDEN
# you can't access both `http://localhost:3001/disallowed` and `http://localhost:3001/disallowed/some/room` (403 Forbidden) but can `http://localhost:3001/allowed/disallowed` (perhaps 404 Not Found)
[[rules]]
when.request.url_path = { value = "cookie", op = "contains" }
respond.text = "Cookie found !"
# test with `curl http://localhost:3001/delicious/cookie/in-the-can`
FAQ (Frequently Asked Questions)
File-based routing
Q: Can I start using the app without extensive initial setup?
A: Yes, absolutely ! You can get the mock server running quickly. Just run npm install -D apimock-rs
followed by npx apimock
(or execute the binary file after downloading it).
The directory where you run the command will be treated as the root directory for the server, and it will respond to HTTP requests by mapping the request paths to the relative paths of files (like .json
files) within the root. The detail about File-based routing is here.
Q: Can the server return responses for HTTP request paths that are directories, not specific files?
A: Yes. For requests like /api/v1/myfunction
(without a file extension), rather than /api/v1/my.json
, you can use an "index" file to provide a response.
If an index.json
, .json5
, .csv
, or .html
file exists within that directory, it will be served as the response for directory access. If none of the index files are present, the server will return 404 NOT FOUND
.
Rule-based routing
Q: I want to map specific HTTP requests to individual responses. Is this possible?
A: While not achievable with file-based routing alone, you can define custom rules in a configuration file to achieve it. How to create it is written below and the detail about Rule-based routing is here.
Q: How do I create a configuration file?
A: Simply run npx apimock --init
. This command will generate a configuration file set in the current directory. You can then edit apimock-rule-set.toml
to customize your routing rules.
Q: Which option is supported as matching condition ?
A: You can use url_path
, method
, headers
and body.json
as conditions, which can be used both alone and combined with each other.
Response via script
Q: Can I dynamically generate responses ?
A: Yes, partially supported with rhai script to determine response file due to request condition. However, static, file-based or rule-based responses are expected to fulfill most cases.
Configuration
Q: Can I switch server port from the default ?
A: Yes. Two ways: run with -p
| --port
argument followed by specific port number. Alternatively, define it in [listener]
section in apimock.toml
, the root configuration. (See Configuration overview.)
Architecture
Q: How are rules loaded ?
A: At server startup.
Q: How are response files loaded ?
A: At each response (via non-blocking file I/O).
Wrapping up
We truly hope this guide helps you get the most out of our mock server and assists you in your work.
You might find other useful examples in our GitHub repository's examples
directory.
If you find this project useful, a star on GitHub is a simple yet powerful way to show your support and helps our community grow.
For questions, bug reports, or feature requests, please open an issue on our GitHub Issues page. We value well-structured feedback and contributions that help improve the project for everyone in the community.
Advanced Topics
Welcome to the advanced guide for experienced users ! Here, we'll dive deeper into controlling your mock server to handle more complex and realistic scenarios. This section will equip you with the knowledge to finely tune your mocking setup and finally unlock the full power of rule-based routing.
How the server decides a response
When your mock server receives an HTTP request, it follows a specific flow to determine which response to return. Understanding this hierarchy is key to structuring your rules effectively:
Response decision flow
graph TD A[HTTP request received] --> B("Middleware(s) configured ?"); B -- Yes --> C["Middleware(s) execution"]; B -- No --> E[Rule-based routing]; C --> D(Response returned ?); D -- Yes --> S[Send response]; D -- No --> E; E --> F(Matched ?); F -- Yes --> S; F -- No --> G[File-based routing]; G --> H(File found ?); H -- Yes --> S; H -- No --> I[Send 404 Not Found];
Steps
-
Middleware(s) execution:
If middlewares are configured, the server executes them first. If a middleware explicitly returns a response, that response is sent immediately. -
Rule-based routing:
If no middleware responds, the server checks your rule sets from top to bottom. The first rule that completely matches the request's conditions will have its response returned. -
File-based routing:
If no rule matches, the server falls back to file-based routing. It tries to find a file matching the request URL path (e.g., by adding.json
,.json5
or.csv
extensions). If found, its content is returned, andContent-Type
is automatically determined. -
404 Not Found:
If no response is determined by any of the above methods, the server returns anHTTP status code 404 Not Found
.
First-match strategy
Throughout this entire flow, the first-match strategy applies. This means the server will always adopt the very first valid response it finds, from middleware to file-based routing, and stop processing further.
Rule Set Configuration File Structure
Your apimock-rule-set.toml
file is structured into two main parts: prefix
and rules
.
-
The
[prefix]
table allows you to define global behaviors or conditions that apply to all rules within that specific rule set file. -
The
[[rules]]
array is where you define your individual mock rules. Each[[rules]]
block represents one rule.
prefix
table
prefix.url_path
This defines a URL path prefix for all rules in this file. Only incoming requests whose URL paths start with this defined prefix will be considered for matching by the rules in this apimock-rule-set.toml
file. This is useful for organizing rules for specific API versions or modules.
- Example: If
prefix.url_path = "/api/v2"
, then a rule withurl_path = "/users"
would effectively match/api/v2/users
.
prefix.respond_dir
When defining a response using respond.file_path
, this prefix is prepended to your specified file path. This helps you organize all your response files in a single, common directory without having to repeat the full path in every rule.
- Example: If
prefix.respond_dir = "responses/"
and a rule hasrespond.file_path = "user_data.json"
, the server will look forresponses/user_data.json
.
Example
# apimock-rule-set.toml
[prefix]
url_path = "/api/v2"
respond_dir = "responses/"
Rules array of tables
[[rules]]
, each rule record, consists of a when
table and a respond
table.
classDiagram direction LR class RuleSet { +Table prefix +Array~Rule~ rules } class Prefix { +String url_path +String respond_dir } class Rule { +Table when +Table respond } class When { +String request.url_path +Table request.headers +Table request.body.json } class Respond { +String file_path +String text +Integer status +String csv_records_key +Integer delay_response_milliseconds } RuleSet --|> Prefix : contains 1 RuleSet --o Rule : contains many Rule --|> When : contains 1 Rule --|> Respond : contains 1 note for When "All conditions are ANDed." note for Respond "Only one of file_path or text can be used."
Here's an overview of the rule data structure in a nested Markdown format:
apimock-rule-set.toml
[prefix]
(Table): Global settings for the rule set file.url_path
: A URL path prefix applied to all rules in this file.respond_dir
: A directory prefix for response file paths in this file.
[[rules]]
(Array of Tables): Defines individual mock rules.
Each[[rules]]
entry represents a single rule and contains:when
(Table): Defines the matching conditions for the request.request.url_path
: Matches the request's URL path.request.headers
: Matches specific HTTP headers (case-insensitive).request.body.json
: Matches values within the JSON request body using dot-notation.- Note: All when conditions within a rule are evaluated with AND logic.
respond
(Table): Specifies the response to be returned if the rule matches.file_path
: Returns content from a file (mutually exclusive with body).text
: Returns a string as the response body (mutually exclusive with file_path).status
: Sets the HTTP status code.csv_records_key
: Replace csv list key which isrecords
by default.delay_response_milliseconds
: Mimic network delay.
Besides, the overall configuration overview is here.
when
data
Each [[rules]]
entry contains a when
table (e.g., when.request.url_path
, when.request.headers
, when.request.body.json
). This table defines the conditions that an incoming HTTP request must meet for this rule to be triggered.
when.request.url_path
Matches the request's URL path.
# apimock-rule-set.toml
[[rules]]
when.request.url_path = "/greetings"
when.request.headers
Matches specific HTTP headers. You can specify multiple header keys.
- Note: According to RFC 9110, HTTP header field names are case-insensitive. Your mock server will handle this automatically.
# apimock-rule-set.toml
[[rules]]
when.request.headers.user = { value = "user1" }
when.request.body.json
Matches content within the request body. Currently, this supports matching specific keys and values within JSON request bodies.
- JSON Body Matching: You define the target key using dot-notation paths (e.g.,
request.body.json.order.items.0.product_id
). For array fields, use the 0-based index number (e.g.,.0
).
# apimock-rule-set.toml
[[rules]]
when.request.body.json.order.items.0.product_id = { value = "123" }
Multiple conditions strategy
Important: If you define multiple conditions (e.g., a path, a header, and a body match) within a single [[rules]]
block, they are all evaluated using AND logic. All conditions must be met for the rule to match the incoming request.
respond
data
The respond
table within each [[rules]]
block specifies the response to be returned when the rule's when
conditions are fully met. You must define one of the following.
respond.file_path
Returns the content of a specified file. The Content-Type
header is automatically determined from the file's extension (e.g., .json
will set application/json
).
# apimock-rule-set.toml
[[rules]]
# when ...
respond.file_path = "response.json"
respond.text
Returns the specified string as the response body. The Content-Type
header is text/plain
.
# apimock-rule-set.toml
[[rules]]
# when ...
respond.text = "My reply !"
respond.status
Sets the HTTP status code for the response (e.g., 200
for OK, 404
for Not Found).
# apimock-rule-set.toml
[[rules]]
# when ...
respond.status = 401
Limitation
You cannot specify both respond.file_path
and respond.text
in the same rule.
Configuration overview
classDiagram direction LR class Config { +Table listener +Table log.verbose +Table service } class ListenerConfig { +String ip_address +Integer port } class LogConfig.VerboseConfig { +Boolean header +Boolean body } class ServiceConfig { +Array~RuleSet~ rule_sets +Array~RuleSet~ middlewares +String fallback_respond_dir } Config --|> ListenerConfig : contains 1 Config --|> LogConfig.VerboseConfig : contains 1 Config --|> ServiceConfig : contains 1
Here's an overview of the rule data structure in a nested Markdown format:
apimock.toml
[listener]
(Table): Server listener.ip_address
port
[log]
(Table): Logger.verbose.header
: Verbose on request header.verbose.body
: Verbose on request body.
[service]
(Table): App servicerule_sets
: Rule-based routing. The detail is here.middlewares
fallback_respond_dir
: File-based routing base. The default is.
, your current directory.
Middleware with Rhai scripts
While your mock server supports middlewares written using Rhai scripts for highly dynamic scenarios, our goal is to minimize the need for custom script creation and maintenance. We believe that file-based and rule-based matching definitions can cover almost all practical needs you'll encounter.
Therefore, we generally do not recommend using middlewares unless you have a very specific and complex requirement that cannot be met by combining rules and operators.
However, for those unique cases, Rhai is a powerful, embedded scripting language that feels very similar to JavaScript or Rust.
Here are some basic Rhai code examples you might use.
Variable Definition
let my_variable = "Hello, Rhai !";
let count = 10;
If / Else Statements
if url_path == "/dynamic_response" {
return "response_for_dynamic.json";\
// else clause is also available:
// } else {
// return "default_response.json";
}
Switch (Match) Statements
switch (url_path) {
"/middleware-test/dummy" if body.middleware == "isHere" => {
// exit() is useful when run in fn (here, equivalent to return statement):
exit(returned_json_file_path);
},
_ => ()
}
Return Statement
Middleware scripts primarily return a file path string. If a middleware returns a value, the server will use it as the response.
#![allow(unused)] fn main() { return "path/to/response.json"; }
To learn more about Rhai's syntax and capabilities
you can refer to the official Rhai documentation. Use this feature judiciously and only when other options fall short.
Technical referrence: A Guide for Developers and Contributors
This document serves as the comprehensive technical reference for our app, designed for developers and contributors. It provides an in-depth understanding of the project's foundational elements.
Herein, you will find detailed information regarding:
- Project Vision and Goals: An outline of the core problems our app aims to solve, its guiding principles.
- Architectural Overview: The necessary context for comprehending the broader system design.
- Detailed Design Specifications: A granular exploration of key design decisions, data structures, algorithms.
Note: This technical reference is currently less mature than our user guide or advanced topics documentation. Contributions to its further development and refinement are always appreciated and considered.
Vision and Goals
Vision
Aims to be a developer-friendly, robust and functional HTTP mock server which doesn't require complicated configuration and accepts rich customization around routing.
Designed in mind with
- Easy setup / usage
- Built as single (and small) executable, integrated configuration. (No need to write scripts, config-less mode is also supported.)
- Performance
- Fast speed, low memory consumption.
- Cross-platform support
Goals
1. Basic
- File-based routing frees us around hard setup
- Supports
.json
/.json5
/.csv
files treated as JSON Response
2. Customization
- Rule-based routing empowers us
- Can specify response time on all or each API path
- Custom HTTP response codes:
3xx
as redirects, and4xx
and5xx
as errors
3. Dynamic processing
- Flexible responses with condition combination. Even with the same API URL path, multiple responses can be returned.
- Optionally, middleware as Rhai scripts is available to customize request routing and response handling
4. Safe and observable usage
- Validates configuration: Missing JSON files, duplicate paths etc.
- Prints out routing at startup
- Describes request content on both HTTP headers and body (json or plain text) when
verbose
on log config is enabled - Integrated test cases for app stability and robustness
5. GUI wrapper integration
- Achieved via cargo feature: When
spawn
is activated, the server is available as subprocess. The output will be returned via tokio mpsc queue.
Architecture
Core Components
-
based on hyper v1
-
src/config.rs
Defines configuration structures for customizable parameters like the server port, root path, logging levels, etc. -
src/server.rs
The HTTP server entry point powered byhyper
. It handles incoming requests and delegates them to the core logic. -
src/core/server/routing.rs
Contains core logic including request routing, matching rules, response rendering, and script evaluation. -
tests/
Includes test cases run bycargo test
.
Design
To facilitate a deeper understanding of the mock server's capabilities and internal workings, this section outlines its core architectural design. It serves as a foundational reference for developers seeking to grasp the system's operational mechanics or contemplating contributions. We will detail the sequential steps involved in server initialization, followed by a comprehensive exploration of the request-to-response lifecycle, each supported by illustrative process diagrams.
Server launch
Workflow
flowchart TD A[get env args] B["app new()"] C["init_logger()"] D(config file path env arg overwriting?) E[replace config or default values with env arg] F[load config] G[validate] H[dump config] I(port env arg overwriting?) J[replace config or default values with env arg] K[start to listen] A --> B --> C --> D D --yes--> E --> F D --no--> F F --> G --> H --> I I --yes--> J --> K I --no--> K
load config
flowchart TD subgraph LoadConfig LC0[load root config] LC1[load middlewares] LC2[load rule sets] subgraph LC3[for each rule set] subgraph LC4[for each rule] LC5[compute derived fields] end end end Note1["note: if any file offered as file path is missing, process will abort"] style Note1 stroke:none LC0 --> LC1 --> LC2 -.- LC3 LC3 --> LC4 LC4 --> LC5 LoadConfig -.- Note1
validate
flowchart TD subgraph Validate A1[root config validate] subgraph For_Rule_Sets A2[validate rule set] subgraph For_Rules A3[validate rule] subgraph For_Whens_Responds A4[validate] end end end end A1 --> For_Rule_Sets A2 --> For_Rules A3 --> For_Whens_Responds
dump config
flowchart TD subgraph Dump_Config A1[dump root config] subgraph For_Each_Rule_Sets A2[dump rule set] subgraph For_Each_Rules A3[dump rule] subgraph For_Whens_Responds A4[dump their children and themselves] end end end end A1 --> For_Each_Rule_Sets A2 --> For_Each_Rules A3 --> For_Whens_Responds
Response
Workflow
flowchart TD A[request received] OP1(OPTIONS method ?) OP2[200 OK] OP3((end)) B[logger dumps request] C(middlewares enabled?) MW[[middleware]] MWC("completed ?") MWCZ((end)) D(rule sets enabled?) RR[[rule-based routing]] RRC("completed ?") RRCZ((end)) FR[[file-based routing]] FRC("completed ?") FRCZ((end)) Z2[not found response] Z2Z((end)) A --> OP1 OP1 -->|Yes| OP2 OP1 -->|No| B OP2 --> OP3 B --> C C -->|Yes| MW C -->|No| D MW --> MWC MWC -->|Yes| MWCZ MWC -->|No| D D --> |Yes| RR D --> |No| FR RR --> RRC RRC -->|Yes| RRCZ RRC -->|No| FR FR --> FRC FRC -->|Yes| FRCZ FRC -->|No| Z2 Z2 --> Z2Z
Middleware
flowchart TD subgraph MW[for each middleware] MW1[run rhai] MW2(file path returned ?) subgraph MWR[return response] MWR1(content got ?) MWR2[return response] MWR3[error response] end Z(("continue")) end MW1 -->MW2 MW2 -->|Yes| MWR MW2 -->|No| Z MWR1 -->|Yes| MWR2 MWR1 -->|No| MWR3
Rule-based routing
flowchart TD subgraph RS[for each rule set] subgraph RR[for each rule] RR1["is_match()"] RR2(matched entry found ?) RR2Z((continue)) subgraph RRR[return response] RRR1(file_path or text ?) RRR2(content got ?) RRR3[return response] RRR4[error response] end end end RR1 -->RR2 RR2 -->|Yes| RRR RR2 -->|No| RR2Z RRR1 -->|file_path| RRR2 RRR1 -->|text| RRR3 RRR2 -->|Yes| RRR3 RRR2 -->|No| RRR4
File-based routing
- try to use request url_path as file path from fallback_respond_dir
- try to find file as it is and then ones which are attached supported extensions to
flowchart TD subgraph FSE["for each raw path or extension-attached"] FSE1(file found ?) FSE1Z((continue)) subgraph FSER[return response] FSER1(file_path or text ?) FSER2(content got ?) FSER3[return response] FSER4[error response] end end FSE1 -->|Yes| FSER FSE1 -->|No| FSE1Z FSER1 -->|file_path| FSER2 FSER1 -->|text| FSER3 FSER2 -->|Yes| FSER3 FSER2 -->|No| FSER4
Response headers
The proper configuration of HTTP response headers is paramount for any API. Even in the context of mock servers, these seemingly minor details significantly influence the stability of communication between the browser (client) and the server, serve as the primary mechanism for CORS (Cross-Origin Resource Sharing) compliance, and ultimately contribute directly to the productivity of API developers
Default response headers
For connection stability
Header | Value |
---|---|
Date | Response date |
Content-Length | Calculated from content body |
Connection | keep-alive |
Cache-Control | no-store |
For security
Header | Value |
---|---|
x-content-type-options | nosniff |
CORS (Cross-Origin Resource Sharing)
Default CORS variables in response
Header | Value |
---|---|
Access-Control-Allow-Methods | GET, POST, PUT, DELETE, OPTIONS |
Access-Control-Allow-Headers | * |
Access-Control-Max-Age | 86400 |
Variation due to request
If Origin
is got from request and looks like authorized request including Cookie
or Authorization
header:
Header | Value |
---|---|
Access-Control-Allow-Origin | Request Origin value |
Vary | Origin |
Access-Control-Allow-Credentials | true |
Else:
Header | Value |
---|---|
Access-Control-Allow-Origin | * |
Vary | * |
Access-Control-Allow-Credentials | (not set) |
Response to OPTIONS
request
It is crucial for the server to immediately respond to OPTIONS
method requests, unlike other HTTP methods. This is because OPTIONS
requests trigger what is known as a "preflight request" in CORS.
The purpose of this preflight OPTIONS
request is to ask the server for permission before sending the actual request. The browser needs to know if the server understands the method, headers, and credentials that will be used in the actual request.
The server must be configured to process OPTIONS
requests as preflight checks, responding with the appropriate CORS headers without attempting to process them as regular API calls. This ensures that the browser receives the necessary permissions to proceed with the actual cross-origin requests, thereby stabilizing browser-server communication according to HTTP and CORS specifications.
Response headers to OPTIONS
request
The headers include the server's default Access-Control-Allow-Methods
, but differ in the key aspects below:
- HTTP Status Code:
204 No Content
Content-Length
:0