δΈζ Β· English
Environment Requirement: Whistle version must be 2.10.0 or higher.
whistle.script is a script extension plugin for Whistle. By writing Node.js scripts in the Web interface, you can inject dynamic logic into Whistle, achieving programmatic deep control over network requests, responses, and protocols like WebSocket.
- Dynamic Rule Generation - Generate and inject Whistle matching rules in real-time based on request URL, headers, and other information.
- Request/Response Interception & Modification - Fully intercept HTTP(S) request and response streams, supporting modification of URL, method, headers, status code, and response body.
- Debugging & Logging - Outputs from
console.logwithin scripts are displayed in the plugin console in real-time, facilitating debugging.
- Bidirectional Communication Interception - Intercept WebSocket handshakes and data frames between client and server.
- Dynamic Message Processing - View, modify, or forward
ping,pong,message, and control frames in real-time. - Direct Data Transmission - Can actively send data to or disconnect from either end.
- Transparent Pipeline Operation - Handle raw TCP connections like HTTPS tunnels, enabling low-level data stream forwarding or modification.
- High Flexibility - Provides an API similar to WebSocket for handling non-HTTP protocols.
Recommended Method (Desktop Users): Download and install the visual client for easier management.
π Whistle Client Download
Command Line Method:
- Install Node.js (>= 8.8)
Download and install the latest LTS version from the Node.js official website. - Install Whistle Globally
npm install -g whistle
Note: If you encounter permission issues during installation, you can try using
sudo(not recommended) or refer to the official documentation to configure the npm global installation path.
After Whistle is running, execute the following command:
w2 i whistle.scriptOr install via the Management Interface:
- Start Whistle and open the management interface (default is
http://127.0.0.1:8899). - Go to the Plugins page.
- Click the
Installbutton at the top. - Enter
whistle.scriptand confirm installation.
- In the Whistle management interface, navigate via the menu
Plugins -> script. - Or directly visit: http://local.whistlejs.com/plugin.script/.
- Click Create in the plugin interface to create a script named
test. - In the editor on the right, enter the following sample code:
exports.handleRequestRules = (ctx) => { console.log('Request received:', ctx.fullUrl); ctx.rules = ['www.example.com 127.0.0.1:8080']; // Forward request to local port 8080 };
- In Whistle's Rules configuration page, add the rule:
www.example.com whistle.script://test
- Now, requests to
http://www.example.comwill be processed by the script, and logs can be viewed in the plugin's Console tab.
This mode allows the script to dynamically return the Whistle rules (string or array) to be executed based on request information. These rules will be merged and executed with the original rules configured via whistle.script://.
Important: To intercept HTTPS requests, you must first enable and install Whistle's HTTPS root certificate.
Script Example (test):
exports.handleRequestRules = (ctx) => {
// Dynamically return a local file based on the request path
if (ctx.fullUrl.includes('/api/test')) {
ctx.rules = ['api.example.com/api/test file://{mockData.json}'];
ctx.values = {
'mockData.json': JSON.stringify({ code: 200, data: 'mocked' })
};
}
};Whistle Rule Configuration:
# Handles requests for multiple domains to the `test` script
whistle.script://test www.test.com api.example.comYou can pass parameters to scripts within rules (avoid spaces within parameters).
whistle.script://test(prod,env1) www.example.comAccess them within the script as follows:
exports.handleRequestRules = (ctx) => {
console.log(process.args); // Output: ["prod", "env1"]
console.log(ctx.scriptValue); // Output (v1.3.0+): "prod,env1"
// Execute different logic based on parameters
ctx.rules = 'www.test.com 127.0.0.1:8080';
};exports.handleWebSocketRules = (ctx) => {
// Dynamically decide which WebSocket connections should be processed by this plugin
this.rules = 'echo.websocket.org statusCode://101';
};This mode grants the script full control over network traffic, allowing it to manually initiate requests, read, and modify data.
Trigger this mode using the script:// protocol.
exports.handleRequest = (ctx, request) => {
const { req, res } = ctx;
req.passThrough({
// Optional
transformReq: function(req, next) {
// getBuffer, getText, getJson can all be used to get the request body, with the same parameter and callback usage
req.getJson(function(err, data) {
if (err) {
return next();
}
// data.a.b.c = 'test';
next(JSON.stringify(data));
});
},
// Optional
transformRes: function(svrRes, next) {
// getBuffer, getText, getJson can all be used to get the request body, with the same parameter and callback usage
svrRes.getText(function(err, text) {
if (err) {
return next();
}
next('[' + text + ', 123' + ']');
});
}
});
};Association Rule:
# Note: Use script:// here to trigger the handleRequest method
www.example.com/api script://testexports.handleWebSocket = async (socket, connect) => {
console.log('WebSocket connection established');
// Connect to the original backend server
const serverSocket = await connect();
// Listen for client messages, forward to server
socket.on('message', (data, opts) => {
console.log('<< From client:', data);
// Data can be modified here
serverSocket.send(`[Relay] ${data}`, opts);
});
// Listen for server messages, forward to client
serverSocket.on('message', (data, opts) => {
console.log('>> From server:', data);
socket.send(data, opts);
});
// Handle connection closure
socket.on('disconnect', (code, reason) => {
console.log(`Client disconnected [${code}]: ${reason}`);
serverSocket.disconnect(code, reason);
});
};Used to handle tunnels established by the CONNECT method (e.g., HTTPS).
exports.handleTunnel = async (clientSocket, connect) => {
const targetSocket = await connect();
// Establish a bidirectional transparent pipeline
clientSocket.pipe(targetSocket).pipe(clientSocket);
// Can listen to the data event for lower-level binary data operations
};Perform identity verification before a request enters other processing stages.
exports.auth = async (req, options) => {
const token = req.headers['x-auth-token'];
// 1. Add internal passthrough headers (starting with x-whistle-)
req.setHeader('x-whistle-req-id', Date.now());
// 2. Perform asynchronous verification
// const isValid = await verifyToken(token);
// return isValid; // Returning false will directly respond with 403 Forbidden
// 3. Allow to pass by default
return true;
};Perform lightweight interception at different lifecycle stages of request/response.
// Process before the request body is read by the Whistle rule engine
exports.handleReqRead = (req, res, options) => {
// Can be used to log the original request body or perform early modifications
req.pipe(res); // Usually direct pipe transmission
};
// Process after the request body is processed by the Whistle rule engine, before being sent to the target server
exports.handleReqWrite = (req, res, options) => {
// Can be used for final modifications based on rule results
req.pipe(res);
};
// Similar hooks: handleResRead, handleResWrite, handleWsReqRead, etc.- Whistle Core Documentation
- Whistle Rule Configuration Syntax
- Whistle GitHub Repository
- Plugin Development Type Definition Reference
This project is open source under the MIT License.
