apimock-rs (API Mock) Documentation
A developer-friendly, featherlight and functional HTTP(S) mock server built in Rust.
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
Easy to start with npm package.
npm install -D apimock-rs
npx apimock

For Users
Contribution
apimock-rs (API Mock) Users Documentation
This guide walks through how to get started with apimock-rs, a mock HTTP(S) server for testing APIs. It covers basic setup, examples, frequently asked questions, deep configuration.
🏞️ Concept Overview
apimock-rs (API Mock) is a developer-friendly, super-lightweight and functional HTTP(S) 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
tokioandhyper.
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/statusreturnsstatus.json. /api/v1/user/1returns the content of1.json./api/v1/user/2returns the content of2.json5..json5is equivalent to.jsonto the server./api/v1/usersreturns the content ofusers.csvas list.- Each of
/,/apiand/api/v1returns 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.tomlapimock-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
equalor 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_cardoperator 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, supported with rhai script to determine response file due to request condition. Moreover, custom JSON or text response body string is directly specified in script. Besides, static, file-based or rule-based responses are expected to fulfill most cases.
Configuration
Listener
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.)
Q: Can I let server listen to the external instead of localhost ?
A: Yes. See listener in Advanced Topics.
Q: Is IPv6 supported ?
A: Yes. See listener in Advanced Topics.
HTTPS support
Q: Can I use HTTPS protocol ?
A: Yes.
Q: Can I build servers to accept both of HTTP and HTTPS ?
A: Yes.
The detail about HTTPS support is here.
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,.json5or.csvextensions). If found, its content is returned, andContent-Typeis 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 isrecordsby 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 {
+ ListenerConfig listener (optional)
+ LogConfig log (optional)
+ ServiceConfig service
}
class ListenerConfig {
+ String ip_address
+ Integer port
+ TlsConfig tls_config (optional)
}
class TlsConfig {
+ String cert
+ String key
+ Integer port (optional)
}
class LogConfig {
+ VerboseConfig verbose
}
class VerboseConfig {
+ Boolean header
+ Boolean body
}
class ServiceConfig {
+ Array~RuleSet~ rule_sets
+ Array~RuleSet~ middlewares
+ String fallback_respond_dir
}
Config "1" o-- "0..1" ListenerConfig
Config "1" o-- "0..1" LogConfig
Config "1" o-- "1" ServiceConfig
ListenerConfig "1" o-- "0..1" TlsConfig
LogConfig "1" o-- "1" VerboseConfig
Here’s an overview of the rule data structure in a nested Markdown format:
apimock.toml[listener]: Server listener.ip_address: IP address.port: Port for either HTTP or HTTPS.tls
[listener.tls]: Server TLS/SSL settings.key: Private key file path.cert: Certificate file path.port: Port for HTTPS.
[log]: Logger.verbose.header: Verbose on request header.verbose.body: Verbose on request body.
[service]: App servicerule_sets: Rule-based routing. The detail is here.middlewaresfallback_respond_dir: File-based routing base. The default is., your current directory.
Middleware: Basics with Rhai scripts
Our mock server supports middlewares written using Rhai scripts for highly dynamic scenarios.
For some 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.
Note: File-based and rule-based routing is preferred
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.
File content response - return Statement
Middleware scripts primarily return a file path string. If a middleware returns a value, the server will use it as the resource file path for response.
#![allow(unused)]
fn main() {
return "path/to/response.json";
}
When the file path is relative
it’s resolved with respect to the .rhai file’s parent directory.
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);
},
_ => ()
}
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.
Middleware: Map-based response variation
Our mock server leverages Rhai scripts to handle dynamic HTTP responses.
Beyond returning a simple string representing a file path, it supports returning map-type values, enabling even greater flexibility in crafting responses. It enables us to return JSON or plain text bodies, providing a dynamic and powerful way to mock complex API behaviors.
Supported Map Keys
The process for generating map-based response is straightforward. You can determine the behavior by specifying a map key: either of file_path, json, text. Any of the associated value should be string.
file_path
The associated value will be treated as a file path. (The same bahavior when a string-type value is returned instead of map)
Example
return #{ "file_path": "some-dir/file.ext" };
json
The server will send an application/json response. The associated value will be used directly as the HTTP response body. This allows you to generate dynamic JSON responses within your Rhai script.
Examples
return #{ "json": "{ \"myjsonkey\": \"myjsonvalue\" }" };
Note: Remember to properly escape double quotes within your JSON string.
let json_str = "{ \"greetings\": \"Hello, world.\" }";
// debug print:
print(url_path);
if url_path == "/middleware-test/map/json" {
return #{ "json": json_str };
}
text
The server will send a text/plain; utf-8 response.
Example
return #{ "text": "Hello, this is a plain text response !" };
Listener
The server listener is customizable with config file. In addition, some of them can be overwritten with startup parameter.
Bound addresses
IPv4
Each of them is available:
Condition: listener.ip_address | Result |
|---|---|
127.0.0.1 (Loopback) | listens to localhost only |
LAN address such as 192.168.1.10 | listens to LAN |
0.0.0.0 | listens to any globally |
Example
Modification as below lets server listen to the external instead of localhost. Note that there should be risk on security.
# apimock.toml
[listener]
- ip_address = "127.0.0.1"
+ ip_address = "0.0.0.0"
IPv6
IPv6 is available.
Example
Loopback
# apimock.toml
[listener]
- ip_address = "127.0.0.1"
+ ip_address = "::1"
Global
Note that there should be risk on security.
# apimock.toml
[listener]
- ip_address = "127.0.0.1"
+ ip_address = "::"
It is equivalent to ::0.
HTTPS support
HTTPS protocol (SSL/TLS) is supported. You can build each of:
- A single server to listen to only HTTPS.
- Servers to listen to both HTTP and HTTPS.
Which port(s) are used ?
The root configuration has two options on port number:
listener.portlistener.tls.port
How they work is:
Condition: listener.tls.port | Result: Port(s) used | Result: Number of servers |
|---|---|---|
| Not specified (omitted) | listener.port is used as HTTPS port. | Only HTTPS listener will start. |
| Specified | listener.tls.port is used as HTTPS port. listener.port is as HTTP. | Both of HTTP and HTTPS listeners will start. |
Configuration example
[listener]
ip_address = "127.0.0.1"
port = 3001
+ [listener.tls]
+ cert = "./cert.pem"
+ key = "./key.pem"
+ # port = 3002
openssl command lines to generate private key and certificate
Note that this is an example to generate self-signed certificates for testing.
openssl genrsa -out key.pem 2048
openssl req -x509 -sha256 -days 365 -key key.pem -out cert.pem -subj "/CN=l
ocalhost"
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, sleek and functional HTTP(S) 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/.csvfiles 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:
3xxas redirects, and4xxand5xxas errors.
3. Dynamic processing
- Flexible responses with condition combination. Even with the same API URL path, multiple responses can be returned.
- 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
verboseon log config is enabled. - Integrated test cases for app stability and robustness.
5. GUI wrapper integration
- Achieved via cargo feature: When
spawnis 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(S) 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(response resource returned ?)
subgraph MWR[return response]
MWR1(resource type ?)
MWR2(which map key found ?)
MWR3(file content got ?)
MWR4(valid json ?)
MWR5[return response]
MWR6[error response]
MWRZ(("continue"))
end
Z(("continue"))
end
MW1 -->MW2
MW2 -->|Yes| MWR
MW2 -->|No| Z
MWR1 -->|string| MWR3
MWR1 -->|map| MWR2
MWR2 -->|file_path| MWR3
MWR2 -->|json| MWR4
MWR2 -->|text| MWR5
MWR2 -->|"(none)"| MWRZ
MWR3 -->|Yes| MWR5
MWR3 -->|No| MWR6
MWR4 -->|Yes| MWR5
MWR4 -->|No| MWR6
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