Skip to content

Commit 8deb246

Browse files
authored
Merge pull request #4 from WoSai/release/redirect_template
支持302重定向,Header支持go template渲染
2 parents 0a25f7d + 2b4b82c commit 8deb246

10 files changed

Lines changed: 318 additions & 42 deletions

File tree

.github/workflows/ci.yml

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,51 @@ name: ci
33
on:
44
pull_request:
55
branches:
6-
- master
6+
- master
77
push:
88
branchs:
9-
- release/*
10-
- hotfix/*
11-
- master
9+
- release/*
10+
- hotfix/*
11+
- master
1212
tags:
13-
- v*
13+
- v*
1414

1515
jobs:
1616
build:
1717
name: Build
1818
runs-on: ubuntu-latest
1919
steps:
20-
- name: Set up Go 1.x
21-
uses: actions/setup-go@v2
22-
with:
23-
go-version: ^1.14
24-
id: go
25-
- name: Check out code into the Go module directory
26-
uses: actions/checkout@v2
27-
- name: Test
28-
run: go test -race -coverprofile=coverage.txt -covermode=atomic -v ./...
29-
- name: Test Coverage Report
30-
run: bash <(curl -s https://codecov.io/bash)
31-
- uses: docker/build-push-action@v1
32-
with:
33-
username: ${{ secrets.DOCKERHUB_ORG_USERNAME }}
34-
password: ${{ secrets.DOCKERHUB_ORG_ACCESS_TOKEN }}
35-
repository: wosai/deepmock
36-
tag_with_ref: true
37-
tag_with_sha: true
20+
- name: Set up Go 1.x
21+
uses: actions/setup-go@v2
22+
with:
23+
go-version: ^1.14
24+
id: go
25+
- name: Check out code into the Go module directory
26+
uses: actions/checkout@v2
27+
- name: Test
28+
run: go test -race -coverprofile=coverage.txt -covermode=atomic -v ./...
29+
- name: Test Coverage Report
30+
run: bash <(curl -s https://codecov.io/bash)
31+
- name: Docker meta
32+
id: meta
33+
uses: docker/metadata-action@v3
34+
with:
35+
images: |
36+
wosai/deepmock
37+
tags: |
38+
type=ref, event=branch
39+
type=ref, event=pr
40+
type=sha
41+
- name: Set up Docker Buildx
42+
uses: docker/setup-buildx-action@v1
43+
- name: Login to DockerHub
44+
uses: docker/login-action@v1
45+
with:
46+
username: ${{ secrets.DOCKERHUB_ORG_USERNAME }}
47+
password: ${{ secrets.DOCKERHUB_ORG_ACCESS_TOKEN }}
48+
- name: Build and push
49+
uses: docker/build-push-action@v2
50+
with:
51+
context: .
52+
push: true
53+
tags: ${{ steps.meta.outputs.tags }}

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616

1717
# vscode
1818
.vscode
19-
.env
19+
.env
20+
/deepmock.log

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Changelog
2+
3+
4+
## 0.6.0 - 2021-12-03
5+
6+
### Added
7+
8+
- 新增对于Response.Header使用Go Template的支持
9+
10+
### Changed
11+
12+
- DTO新增支持字段render_header和header_template
13+
- Github Action升级docker/build-push-action@v2
14+
15+
### Fixed
16+
17+
- 修复创建规则时,StatusCode设置不生效的问题

README.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ curl -X POST http://127.0.0.1:16600/api/v1/rule \
4949
* `exact`: 精确筛选
5050
* `keyword`: 关键字筛选
5151
* `regular`: 正则表达式筛选
52-
- Response可以通过[Go Template](https://golang.org/pkg/text/template/)实现,因此可以支持以下特性
52+
- Response中的body和header均可以通过[Go Template](https://golang.org/pkg/text/template/)实现,因此可以支持以下特性
5353
* 可以使用逻辑控制,如: `if``range`
5454
* 可以使用内置函数
5555
* 可以自定义函数
5656
- 规则中的`Variable``Weight`以及请求中的`Header``Query``Form``Json`同样参与Response模板的渲染
57+
- Response.body中使用template渲染时,需设置`is_template: true`
58+
- Response.header可采用Patch形式,使用template渲染部分Header字段,需设置`render_template: true`以及template字符串`header_template`
5759

5860
### 接口列表:
5961

@@ -397,7 +399,54 @@ curl http://127.0.0.1:16600/api/v1/rule/bba079deaa2b97037694a89386616d88
397399
|`timestamp` | `precision` | `{{timestamp ms}}` | 按指定的精度返回unix时间戳:mcs,ms,sec|
398400
|`plus`| `v`, `i` | `{{plus v i}}` | 将v的值增加i,实现简单的计算,支持string\int\float类型|
399401
|`rand_string`| `n` | `{{rand_string n}}`| 生成长度为n的随机字符串 |
402+
|`html_unescaped`||`{{.Variable.str `&#x7c;`html_unescaped}}`| 防止template渲染时,将部分字符进行html编码,如"&" --> "\&amp;" |
400403

404+
#### Response.Header使用Template渲染示例
405+
```json
406+
{
407+
"path": "/redirect/baidu",
408+
"method": "get",
409+
"variable": {
410+
"app_id": "app_id",
411+
"code": "123456"
412+
},
413+
"responses": [
414+
{
415+
"is_default": true,
416+
"response": {
417+
"is_template": false,
418+
"render_header": true,
419+
"header": {
420+
"Content-Type": "text/html",
421+
"User-Agent": "Wechat",
422+
"x-env-flag": "test-111",
423+
"rand-string": "I'm a {{rand_string}}",
424+
"timestamp-sec": "2021-01-01",
425+
"uuid": "I'm a {{uuid}}",
426+
"location": "https://www.bing.com"
427+
},
428+
"header_template": "{\"location\": \"{{.Query.redirect_uri | html_unescaped}}&state={{.Query.state}}&app_id={{.Variable.app_id}}&auth_code={{.Variable.code}}\",\"rand-string\":\"{{rand_string 20}}\",\"uuid\":\"{{uuid}}\", \"timestamp-sec\":\"{{timestamp \"sec\"}}\"}",
429+
"status_code": 302
430+
}
431+
}
432+
]
433+
}
434+
```
435+
```
436+
curl -I --location --request GET 'http://localhost:16600/redirect/baidu?redirect_uri=https%3A%2F%2Fwww.baidu.com%3Fref%3Dhello&appid=appid&state=true'
437+
438+
HTTP/1.1 302 Found
439+
Server: DeepMock Service
440+
Date: Fri, 03 Dec 2021 03:49:55 GMT
441+
Content-Type: text/html
442+
Content-Length: 0
443+
Location: https://www.baidu.com?ref=hello&state=true&app_id=app_id&auth_code=123456
444+
Rand-String: YHYEglVGzYB8FIIKbify
445+
Timestamp-Sec: 1638503396
446+
Uuid: 1cb6c914-04d7-40fe-908f-73d202bb63ea
447+
X-Env-Flag: test-111
448+
User-Agent: Wechat
449+
```
401450

402451
### Benchmark
403452

application/rule.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,12 @@ func convertRegulationDTO(reg *types.RegulationDTO) *domain.Regulation {
9090
if reg.Template != nil {
9191
r.Template = &domain.Template{
9292
IsTemplate: reg.Template.IsTemplate,
93+
RenderHeader: reg.Template.RenderHeader,
9394
Header: reg.Template.Header,
95+
HeaderTemplate: reg.Template.HeaderTemplate,
9496
Body: reg.Template.Body,
9597
B64EncodedBody: reg.Template.B64EncodeBody,
98+
StatusCode: reg.Template.StatusCode, // 默认不传,设置为200
9699
}
97100
if reg.Template.StatusCode == 0 {
98101
r.Template.StatusCode = http.StatusOK
@@ -126,11 +129,13 @@ func convertRegulationVO(reg *domain.Regulation) *types.RegulationDTO {
126129
r := &types.RegulationDTO{
127130
IsDefault: reg.IsDefault,
128131
Template: &types.TemplateDTO{
129-
IsTemplate: reg.Template.IsTemplate,
130-
Header: reg.Template.Header,
131-
StatusCode: reg.Template.StatusCode,
132-
Body: reg.Template.Body,
133-
B64EncodeBody: reg.Template.B64EncodedBody,
132+
IsTemplate: reg.Template.IsTemplate,
133+
RenderHeader: reg.Template.RenderHeader,
134+
Header: reg.Template.Header,
135+
HeaderTemplate: reg.Template.HeaderTemplate,
136+
StatusCode: reg.Template.StatusCode,
137+
Body: reg.Template.Body,
138+
B64EncodeBody: reg.Template.B64EncodedBody,
134139
},
135140
}
136141

domain/executor.go

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ type (
6767
// TemplateExecutor 响应报文模板执行器
6868
TemplateExecutor struct {
6969
IsGolangTemplate bool
70+
RenderHeader bool
7071
IsBinData bool
7172
template *template.Template
73+
headerTemplate *template.Template
7274
header *fasthttp.ResponseHeader
7375
body []byte
7476
}
@@ -270,24 +272,58 @@ func (fe *FilterExecutor) Filter(request *fasthttp.Request) bool {
270272
// Render 渲染函数
271273
func (te *TemplateExecutor) Render(ctx *fasthttp.RequestCtx, v map[string]interface{}, weight map[string]string) error {
272274
te.header.CopyTo(&ctx.Response.Header)
275+
rc := &RenderContext{}
276+
if te.RenderHeader {
277+
// 渲染header template
278+
if err := te.handleHeaderTemplate(rc, ctx, v, weight); err != nil {
279+
return err
280+
}
281+
}
273282
if !te.IsGolangTemplate {
274283
ctx.Response.SetBody(te.body)
275284
return nil
276285
}
277286

278-
// 开始渲染模板
279-
var rc RenderContext
287+
// 开始渲染body模板
288+
if rc == nil {
289+
rc.parseParams(ctx, v, weight)
290+
}
291+
return te.template.Execute(ctx.Response.BodyWriter(), rc)
292+
}
293+
294+
// handleHeaderTemplate 处理header中的template
295+
func (te *TemplateExecutor) handleHeaderTemplate(rc *RenderContext, ctx *fasthttp.RequestCtx, v map[string]interface{}, weight map[string]string) error {
296+
// parse params
297+
rc.parseParams(ctx, v, weight)
298+
// render template
299+
var buf bytes.Buffer
300+
if err := te.headerTemplate.Execute(&buf, rc); err != nil {
301+
misc.Logger.Error(err.Error())
302+
return err
303+
}
304+
// merge to ctx.response.header
305+
headerToBeSet := make(map[string]string)
306+
if err := json.Unmarshal(buf.Bytes(), &headerToBeSet); err != nil {
307+
misc.Logger.Error(err.Error())
308+
return err
309+
}
310+
for k, v := range headerToBeSet {
311+
ctx.Response.Header.Set(k, v)
312+
}
313+
return nil
314+
}
315+
316+
// parseParams 解析Request中的参数,供template渲染使用
317+
func (rc *RenderContext) parseParams(ctx *fasthttp.RequestCtx, v map[string]interface{}, weight map[string]string) {
280318
h := extractHeaderAsParams(&ctx.Request)
281319
q := extractQueryAsParams(&ctx.Request)
282320
f, j := extractBodyAsParams(&ctx.Request)
283-
284321
rc.Variable = v
285322
rc.Weight = weight
286323
rc.Header = h
287324
rc.Query = q
288325
rc.Form = f
289326
rc.Json = j
290-
return te.template.Execute(ctx.Response.BodyWriter(), rc)
291327
}
292328

293329
// Render 渲染函数
@@ -376,6 +412,11 @@ func dateDelta(date, layout string, year, month, day int) string {
376412
return t.AddDate(year, month, day).Format(layout)
377413
}
378414

415+
func HTMLUnescaped(x string) interface{} {
416+
// do not encode HTML, like "&" --> "&amp;"
417+
return template.HTML(x)
418+
}
419+
379420
func init() {
380421
// create build-in template functions
381422
defaultTemplateFuncs = make(template.FuncMap)
@@ -385,4 +426,5 @@ func init() {
385426
_ = RegisterTemplateFunc("plus", plus)
386427
_ = RegisterTemplateFunc("rand_string", misc.GenRandomString)
387428
_ = RegisterTemplateFunc("date_delta", dateDelta)
429+
_ = RegisterTemplateFunc("html_unescaped", HTMLUnescaped)
388430
}

0 commit comments

Comments
 (0)