# Dalao-proxy
An expandable HTTP proxy based on the plug-in system for frontend developers with request caching request mock and development!

> An one-line command server! More light-weight and convenient than the proxy of `webpack-dev-server` in daily development.

[![version](https://img.shields.io/npm/v/dalao-proxy.svg)](https://www.npmjs.com/package/dalao-proxy)
[![](https://img.shields.io/npm/dt/dalao-proxy.svg)](https://github.com/CalvinVon/dalao-proxy)
[![Package Quality](https://npm.packagequality.com/shield/dalao-proxy.svg)](https://packagequality.com/#?package=dalao-proxy)
![dependencies](https://img.shields.io/david/CalvinVon/dalao-proxy.svg)

[English Doc](https://github.com/CalvinVon/dalao-proxy/blob/master/README.md)
|
[中文文档](https://github.com/CalvinVon/dalao-proxy/blob/master/README_CN.md)

## Features
- HTTP proxy
- HTTP capture
- Request mock
- Request auto cache with flexible configuration
- Auto-generate config file
- Auto reload server when config file changes
- Support fast switching of multiple environments
- Expandable and plugin-based system

![v0.9.2 preview](https://raw.githubusercontent.com/CalvinVon/dalao-proxy/master/.github/screenshot/start.png)

# Table of contents
- [Getting Started](#Getting-Started)
    - [Install](#Install)
    - [Configure](#Configure)
    - [Start proxy](#Start-proxy)
    - [Enjoy It](#Enjoy-It)
- [Commands](#Commands)
- [Start Cache Request Response](#Start-Cache-Request-Response)
    - [Example](#Example)
    - [`Never Read Cache` Mode](#Never-Read-Cache-Mode)
    - [`Read Cache` Mode](#Read-Cache-Mode)
- [Start Request Mock](#Start-Request-Mock)
- [Docs](#Docs)
    - [Configuration file](#configuration-file)
        - [Option `host`](#Option-host)
        - [Option `watch`](#Option-watch)
        - [Option `headers`](#Option-headers)
        - [Option `cache`](#Option-cache)
        - [Option `cacheContentType`](#Option-cacheContentType)
        - [Option `cacheMaxAge`](#Option-cacheMaxAge)
        - [Option `responseFilter`](#Option-responseFilter)
        - [Option `proxyTable`](#Option-proxyTable)
        - [Proxy `route` config](#Proxy-route-config)
            - [Route option `pathRewrite`](#Route-option-pathRewrite)
- [Plugin System](#Plugin-Systembeta)
    - [Install Plugin](#Install-Plugin)
        - [Global Install Plugin](#Global-Install-Plugin)
        - [Local Install Plugin](#Local-Install-Plugin)
    - [Available Plugins](#Available-Plugins)
    - [Lifecycle Hook](#Lifecycle-Hook)
        - [beforeCreate](#beforeCreate)
        - [onRequest](#onRequest)
        - [onRouteMatch](#onRouteMatch)
        - [beforeProxy](#beforeProxy)
        - [afterProxy](#afterProxy)

# Getting Started
## Install
```bash
$ npm i dalao-proxy -g
```

## Configure
Default configuration file will be generated in `dalao.config.json`.
```bash
# This utility will walk you through creating a config file
$ dalao-proxy init

# Generate config file directly
$ dalao-proxy init -y
```

## Start proxy
```bash
# dalao will read default config file
$ dalao-proxy start

# custom options
dalao-proxy start -wc --config ./dalao.config.json
```
Start Options
```
Options:
    -C, --config [filepath]     use custom config file
    -w, --watch                 reload when config file changes
    -P, --port [port]           custom proxy port
    -H, --host [hostname]       dynamic add host linked to your server address
    -t, --target [proxyTarget]  target server to proxy
    -c, --cache                 enable request cache
    -i, --logger                  enable log print
    -h, --help                  output usage information
```

🎉  Congratulations, your one-line command proxy server started, now you have your own *dalao*!

## Enjoy it!
Every single modification of the configuration file, the `dalao` will automatically restart and output prompts.

[back to menu](#Table-of-contents)

# Commands
```bash
$ dalao-proxy --help
Usage: dalao-proxy [options] [command]

Options:
  -V, --version                      output the version number
  -h, --help                         output usage information

Commands:
  start [options]                    auto detect config & start proxy server
  init [options]                     create an init config file in current folder
  mock [options] <method>            create a mock file in json format
  clean [options]                    clean cache files
  add-plugin [options] <pluginName>  add plugin globally
```


# Start Cache Request Response
1. Set option `cache` to `true`
1. Set appropriate value for `cacheContentType`， `cacheMaxAge`，`responseFilter` options

    When those three fields satisfied certain conditions, request response would be cached in folder (`cacheDirname` you specified).

## Example:
Here is an simple example of server response data
```js
// send request
POST /api/list HTTP/1.1
...

// get response
connection: keep-alive
content-encoding: gzip
content-type: application/json; charset=UTF-8
date: Fri, 19 Apr 2019 08:35:42 GMT
server: nginx/1.10.3 (Ubuntu)
transfer-encoding: chunked
vary: Accept-Encoding
// response data
{
    "status": 1,
    "data": {
        "list": [
            { "id": 1, "name": "dalao" },
            { "id": 2, "name": "proxy" }
        ],
        "total": 2
    }
}
```

The config should be like this:
```js
"cache": true,
"cacheContentType": ["application/json"],
"responseFilter": ["status", 1],
```

## `Never Read Cache` Mode
If you just want to cache response only and get a real proxy response

> **Recommended** when you have completed frontend and backend API docking or requiring high accuracy of response data.

> When the backend service crashes during development, you can switch to [**Read Cache** mode](#Read-Cache-Mode) to **create a fake backend service**.

Set option `cacheMaxAge` to *Never Read Cache* mode
```js
"cacheMaxAge": ["s", 0]
```

## `Read Cache` Mode
When you're ready to develop front-end pages or need [request mock](#Start-Request-Mock)

> `dalao-proxy` would try to look up cache/mock file first, then return a real response after the failure.

> **Recommended:** The easier way is to delete `CACHE_TIME` field in the cached JSON files rather than frequent restarts of the service because of modifying config file.(Updated at **v0.8.3**)

Set option `cacheMaxAge` to *Read Cache* mode. [See option `cacheMaxAge`](#Option-cacheMaxAge)


```js
// set permanent request cache
"cacheMaxAge": ["s", "*"]
"cacheMaxAge": ["second", "*"]
// set certain expire time request cache (5 min)
"cacheMaxAge": ["m", 5]
"cacheMaxAge": ["minute", 5]
```

[back to menu](#Table-of-contents)

# Start Request Mock
> **Updated at v0.9.0** Now, `dalao-proxy` support Javascript-style cache file, so you can import any dependencies to mock your data. For example using [`Mock.js`](https://github.com/nuysoft/Mock/wiki/Getting-Started)

Type `dalao-proxy mock <HTTP method>` and the HTTP method you want to mock
```bash
# dalao-proxy mock [options] <method>
$ dalao-proxy mock post
> Request url: /api/list

Mock file created in /home/$(USER)/$(CWD)/.dalao-cache/GET_api_get.json


$ dalao-proxy mock post --js
> Request url: /api/list

Mock file created in /home/$(USER)/$(CWD)/.dalao-cache/GET_api_get.js
```
Put some mock data into `GET_api_get.json` file or do whatever you want in js file, then you can access `/api/list` to get your mock data.
```json
{
    "data": {
        "list": ["mock", "data"]
    },
    "code": 200
}
```
```js
const mockjs = require('mockjs');
const list = Mock.mock({
    'list|1-10': [{
        'id|+1': 1
    }]
});

module.exports = {
    data: list,
    code: 200
};
```

[back to menu](#Table-of-contents)



# Docs
## Configuration file
Dalao will look up the config file in the current working directory while starting up.

Default config filename is `dalao.config.json`
```js
{
    // config file name
    "configFileName": "dalao.config.json",
    // cache file store
    "cacheDirname": ".dalao-cache",
    // enable reload when config file changes
    "watch": true,
    // proxy server host
    "host": "localhost",
    // proxy server port
    "port": 8000,
    // proxy target (base setting)
    "target": "target.example.com",
    // enable proxy request cache (base setting)
    "cache": false,
    // define response type to cache (base setting)
    "cacheContentType": [
        "application/json"
    ],
    // define cache valid max time before expired
    "cacheMaxAge": [
        "second",
        0
    ],
    // define response body filter
    "responseFilter": [],
    // enable logger
    "logger": false,
    // show debug message
    "debug": false,
    // custom response headers
    "headers": {},
    // proxy rule table
    "proxyTable": {
        // proxy match rule
        "/": {
            "path": "/"
        }
    },
    // extra plugins
    "plugins": []
}
```

### Option `host`
- type: **string**
- default: `localhost`

    > When configured as `0.0.0.0`, other devices on the LAN can also access the service, and the local machine can access using `localhost`.

### Option `watch`
- type: **boolean**
- default: `true`

Enable proxy server auto reload when config file changes


### Option `headers`
- type: **Object**

Adding custom **request headers** or **response headers** ([Updated at **v0.9.11**](./CHANGELOG.md#0911-2019-10-10)).

Example:
```json
{
    "headers": {
        "request": {
            "Token": "THIS-IS-YOUR-FAKE-TOKEN"
        },
        "response": {
            "Authorization": "THIS-IS-YOUR-FAKE-AUTHORIZATION"
        }
    }
}
```
or **only setting response headers** by default (backward compatible).
```json
{
    "headers": {
        "Authorization": "THIS-IS-YOUR-FAKE-AUTHORIZATION"
    }
}
```

### Option `cache`
- type: **boolean**
- default: `true`

    Enable request cache when response satisfies [certain conditions](https://github.com/CalvinVon/dalao-proxy#Start-Cache-Request-Response).
    > When a request has been cached, extra field `X-Cache-Request` will be added into response headers.

### Option `cacheContentType`
- *precondition: when `cache` option is `true`*
- type: **Array**
- default: `['application/json']`

    Cache filtering by response content type with at least one item matches.
    *Support `RegExp` expression*

### Option `cacheMaxAge`
- *precondition: when `cache` option is `true`*
- type: **Array**
    - cacheMaxAge[0]: cache expire time unit
    - cacheMaxAge[1]: cache expire time digit
        - when digit comes to `0`, `dalao-proxy` will **never** try to look up cache file (but still cache request-response) regardless of expire time. 
        - when digit comes to special value `'*'`, which means cache file will **never expire**, and `dalao-proxy` will first try to read and return the cache file, and if it is not found, it would return the real request-response.
- default: `['second', 0]`

    Cache filtering by cache file expires time.
    > Support quick restart and take effect immediately.

    > `X-Cache-Expire-Time` and `X-Cache-Rest-Time` fields will be included in response headers.

### Option `responseFilter`
- *precondition: when `cache` option is `true`*
- type: **Array**
    - responseFilter[0]: response body field for filtering
    - responseFilter[1]: valid value for filtering
- default: `['code', 200]`

Cache filtering by response body data. *Not HTTP status code*

### Option `plugins`
- type: **Array**

    A list of plugin npm *package name*.

    You will need to add plugins to expand the expandability of `dalao-proxy`. See [Plugins](#Plugins).

### Option `proxyTable`
- type: **Object**
- default: `{ "/": { "path": "/" } }`

    Proxy [route](#Proxy-route-config) map set.

### Proxy `route` config
```js
{
    // proxy target path
    // default: `/`
    path
    // proxy target
    // extend base config option `target`
    target,
    // proxy target path rewrite
    pathRewrite,
    // route custom config
    // default: extend base config
    cache,
    cacheContentType，
    cacheMaxAge,
    responseFilter,
}
```
#### Route option `pathRewrite`
Use `RegExp` expression to match target path, and replace with rewrite value.

Example:
```js
"pathRewrite": {
    "^/api": ""
}
```

`"/api/user/list"` will be replaced to be `"/user/list"`

[back to menu](#Table-of-contents)


# Plugin System[Beta]
`Dalao-proxy` support custom plugins now by using option [`plugins`](#Option-plugins).
> **Note**: Reinstalling `dalao-proxy` will cause globally installed plugins to fail (local installation is not affected) and you will need to reinstall the required plugins globally.

## Install Plugin
### Global Install Plugin
```bash
# Globally install
$ dalao-proxy add-plugin <plugin name>

# Globally uninstall
$ dalao-proxy add-plugin -d <plugin name>
```
### Local Install Plugin
```bash
$ npm install -D dalao-proxy
$ npm install -D <plugin name>
```
Generate config json file
```bash
$ npx dalao-proxy init
```

Add plugin in config json file
```json
{
    "plugins": [
        "<plugin name>"
    ]
}
```

Then in package.json
```json
{
    "scripts": {
        "proxy": "dalao-proxy start"
    }
}
```

You can develop your plugins to expand the ability of `dalao-proxy`.
## Available Plugins
- [*Build in*] [**check-version**](https://github.com/CalvinVon/dalao-proxy/tree/master/src/plugin/check-version)

    The dalao-proxy will automaticly check the latest version.

- [*Build in*] [**proxy-cache**](https://github.com/CalvinVon/dalao-proxy/tree/master/src/plugin/proxy-cache)

    Doing awesome request cache and mock work.

- [**@calvin_von/proxy-plugin-monitor**](https://github.com/CalvinVon/dalao-proxy/tree/master/packages/%40calvin_von/proxy-plugin-monitor) A dalao-proxy plugin for request monitoring.
    > Look at where the dalao-proxy forwarded the request.
- [*New*] [**@calvin_von/proxy-plugin-redirect**](https://github.com/CalvinVon/dalao-proxy/tree/master/packages/%40calvin_von/proxy-plugin-redirect) A dalao-proxy plugin for request redirect.
    > Awesome plugin for debugging the online program locally.
## Lifecycle Hook
`Dalao-proxy` provides bellowing lifecycle hooks among different proxy periods.
> Note: All `context` parameters given are not read-only, you can modify and override the values at will.

> **Best Practices**: Each plugin produces its own context data under the `context` parameter, and appropriately modifies the behavior of `dalao-proxy` and its plugins according to the order in which the plugins are executed.
### `beforeCreate`
> You can do some initial operations here.
- type: `Function`
- params
    - `context`
        - `context.config`: parsed config object.
- detail:

    Invoked before proxy server created.

### `onRequest`
- type: `Function`
- params
    - `context`
        - `context.config`: parsed config object.
        - `context.request`: request received by the proxy server. Instance of `http.IncomingMessage`
        - `context.response`: response that proxy sever need to return. Instance of `http.ServerResponse`
    - `next`
        - type: `Function`
        - params: `error`/`interruptMessage`
            - If an `error` param passed in, the request would be interrupted because of throwing an error.
            - If a `string` param passed in, it would be seen as a `PluginInterrupt` without throwing an error.

        A `next` function must be called to enter the next period. 
- detail:

    Invoked when a request received.

### `onRouteMatch`
- type: `Function`
- params
    - `context`
        - `context.config`: parsed config object
        - `context.request`: request received by the proxy server
        - `context.response`: response that proxy sever need to return
        - `context.matched`
            - `path`: matched path according to request URL.
            - `route`: matched route object.
            - `notFound`: whether the route is found.
    - `next`
        - type: `Function`
        - params: `error`/`interruptMessage`
            - If an `error` param passed in, the request would be interrupted because of throwing an error.
            - If a `string` param passed in, it would be seen as a `PluginInterrupt` without throwing an error.

        A `next` function must be called to enter the next period.
- detail:

    Invoked when a request URL matches given `proxyTable` rules.

### `beforeProxy`
- type: `Function`
- params
    - `context`
        - `context.config`: parsed config object
        - `context.request`: request received by the proxy server
        - `context.response`: response that proxy sever need to return
        - `context.matched`
            - `path`: matched path according to request URL.
            - `route`: matched route object.
        - `context.proxy`
            - `uri`: the converted URI address.
            - `route`: matched route object.
    - `next`
        - type: `Function`
        - params: `error`/`interruptMessage`
            - If an `error` param passed in, the request would be interrupted because of throwing an error.
            - If a `string` param passed in, it would be seen as a `PluginInterrupt` without throwing an error.

        A `next` function must be called to enter the next period.
- detail:

    Invoked before `dalao-proxy` start to send a proxy request.

### `afterProxy`
- type: `Function`
- params
    - `context`
        - `context.config`: parsed config object
        - `context.request`: request received by the proxy server
        - `context.response`: response that proxy sever need to return
        - `context.matched`
            - `path`: matched path according to request URL.
            - `route`: matched route object.
        - `context.proxy`
            - `uri`: the converted URI address.c
            - `route`: matched route object.
            - `request`: proxy request object. Instance of `request.Request`. see [request/request on Github](https://github.com/request/request#streaming)
            - `response`: proxy response object. Instance of `request.Response`.
        - `context.data`
            - `error`: proxy request error. instance of `Error`.
            - `request`
                - `rawBody`: raw data of request body
                - `body`: parsed data of request body
                - `query`: parsed data of request query
                - `type`: content type of request
            - `response`
                - `rawBody`: raw data of response body of proxy
                - `body`: parsed data of response body of proxy
                - `type`: content type of response of proxy
                - `size`: content size of response of proxy
                - `encode`: content type of response of proxy
    - `next`
        - type: `Function`
        - params: `error`/`interruptMessage`
            - If an `error` param passed in, the request would be interrupted because of throwing an error.
            - If a `string` param passed in, it would be seen as a `PluginInterrupt` without throwing an error.

        A `next` function must be called to enter the next period.
- detail:

    Invoked after `dalao-proxy` has sent a proxy request and has resolved all request and response data.

[back to menu](#Table-of-contents)

# LICENSE
[MIT LICENSE](https://github.com/CalvinVon/dalao-proxy/blob/master/LICENSE)