Skip to content

Commit 5a76040

Browse files
committed
more
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
1 parent 0b00a54 commit 5a76040

3 files changed

Lines changed: 272 additions & 11 deletions

File tree

go/javascript.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package main
22

33
import (
44
"fmt"
5+
"io"
56
"log"
67
"math/rand"
8+
"os"
79
"strings"
810
"sync"
911

@@ -39,18 +41,12 @@ type (
3941
}
4042
)
4143

42-
func newJavaScriptFilterConfig(script string) gosdk.HttpFilterConfig {
44+
func newJavaScriptFilterConfig(userCode string) gosdk.HttpFilterConfig {
4345
c := &javaScriptFilterConfig{}
4446

45-
script = strings.Join([]string{
46-
script,
47-
fmt.Sprintf(functionDeclTemplate, javaScriptExportedSymbolOnConfig),
48-
fmt.Sprintf(functionDeclTemplate, javaScriptExportedSymbolOnRequestHeaders),
49-
fmt.Sprintf(functionDeclTemplate, javaScriptExportedSymbolOnResponseHeaders),
50-
}, "\n")
51-
47+
script := completeJavaScriptCode(userCode)
5248
for i := range numberOfVMPool {
53-
vm, err := newJavaScriptVM(script)
49+
vm, err := newJavaScriptVM(script, os.Stdout)
5450
if err != nil {
5551
log.Printf("failed to create JavaScript VM: %v", err)
5652
return nil
@@ -60,15 +56,24 @@ func newJavaScriptFilterConfig(script string) gosdk.HttpFilterConfig {
6056
return c
6157
}
6258

63-
func newJavaScriptVM(script string) (*javaScriptVM, error) {
59+
func completeJavaScriptCode(userCode string) string {
60+
return strings.Join([]string{
61+
userCode,
62+
fmt.Sprintf(functionDeclTemplate, javaScriptExportedSymbolOnConfig),
63+
fmt.Sprintf(functionDeclTemplate, javaScriptExportedSymbolOnRequestHeaders),
64+
fmt.Sprintf(functionDeclTemplate, javaScriptExportedSymbolOnResponseHeaders),
65+
}, "\n")
66+
}
67+
68+
func newJavaScriptVM(script string, w io.Writer) (*javaScriptVM, error) {
6469
vm := goja.New()
6570
console := vm.NewObject()
6671
err := console.Set("log", func(call goja.FunctionCall) goja.Value {
6772
args := make([]interface{}, 0, len(call.Arguments))
6873
for _, a := range call.Arguments {
6974
args = append(args, a.Export())
7075
}
71-
fmt.Println(args...)
76+
_, _ = fmt.Fprint(w, args...)
7277
return goja.Undefined()
7378
})
7479
if err != nil {
@@ -183,6 +188,10 @@ func (p *javaScriptFilter) ResponseHeaders(e gosdk.EnvoyHttpFilter, _ bool) gosd
183188
e.SetResponseHeader(key, []byte(value))
184189
return goja.Undefined()
185190
})
191+
if _, err := vm.onResponseHeaders(goja.Undefined(), obj); err != nil {
192+
log.Printf("failed to call %s: %v", javaScriptExportedSymbolOnResponseHeaders, err)
193+
return gosdk.ResponseHeadersStatusStopIteration
194+
}
186195
return gosdk.ResponseHeadersStatusContinue
187196
}
188197

go/javascript_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,134 @@
11
package main
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/envoyproxy/dynamic-modules-examples/go/gosdk"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func Test_newJavaScriptFilterConfig(t *testing.T) {
12+
f := newJavaScriptFilterConfig(`
13+
function OnConfigure () {}
14+
function OnRequestHeaders(ctx) {}
15+
function OnResponseHeaders(ctx) {}
16+
`)
17+
require.NotNil(t, f)
18+
}
19+
20+
func Test_newJavasScriptVM(t *testing.T) {
21+
for _, tc := range []struct {
22+
name string
23+
script string
24+
expOut string
25+
expErr string
26+
}{
27+
{
28+
name: "valid script with all functions",
29+
expOut: `OnConfigure called`,
30+
script: `
31+
function OnConfigure () {
32+
console.log("OnConfigure called");
33+
}
34+
function OnRequestHeaders(ctx) {
35+
console.log("OnRequestHeader called");
36+
}
37+
function OnResponseHeaders(ctx) {
38+
console.log("OnResponseHeader called");
39+
}
40+
`,
41+
},
42+
{
43+
name: "invalid script with missing functions",
44+
script: `
45+
function OnConfigure () {
46+
console.log("OnConfigure called");
47+
}
48+
`,
49+
expErr: `failed to get OnRequestHeaders function`,
50+
},
51+
{
52+
name: "invalid script",
53+
script: `invalid`,
54+
expErr: `failed to run script: ReferenceError: invalid is not defined at <eval>:1:1(0)`,
55+
},
56+
} {
57+
t.Run(tc.name, func(t *testing.T) {
58+
logout := &bytes.Buffer{}
59+
_, err := newJavaScriptVM(tc.script, logout)
60+
if tc.expErr == "" {
61+
require.Equal(t, tc.expOut, logout.String())
62+
require.NoError(t, err)
63+
} else {
64+
require.ErrorContains(t, err, tc.expErr)
65+
}
66+
})
67+
}
68+
}
69+
70+
func Test_javaScriptFilter_RequestHeaders(t *testing.T) {
71+
logout := &bytes.Buffer{}
72+
vm, err := newJavaScriptVM(
73+
`function OnConfigure () {}
74+
function OnRequestHeaders(ctx) {
75+
ctx.setRequestHeader("x-hello", "world");
76+
let reqId = ctx.getRequestHeader("x-request-id");
77+
console.log("Request ID: ", reqId);
78+
}
79+
function OnResponseHeaders(ctx) {}`, logout)
80+
require.NoError(t, err)
81+
82+
f := &javaScriptFilter{vm: vm, requestHeaders: map[string]string{
83+
"x-request-id": "12345",
84+
}}
85+
called := false
86+
m := &mockEnvoyHttpFilter{
87+
getRequestHeaders: func() map[string][]string { return map[string][]string{"x-request-id": {"12345"}} },
88+
setRequestHeader: func(key string, value []byte) bool {
89+
require.Equal(t, "x-hello", key)
90+
require.Equal(t, "world", string(value))
91+
called = true
92+
return true
93+
},
94+
}
95+
96+
status := f.RequestHeaders(m, false)
97+
require.Equal(t, gosdk.RequestHeadersStatusContinue, status)
98+
require.True(t, called)
99+
100+
require.Contains(t, logout.String(), "Request ID: 12345")
101+
}
102+
103+
func Test_javaScriptFilter_ResponseHeaders(t *testing.T) {
104+
logout := &bytes.Buffer{}
105+
vm, err := newJavaScriptVM(
106+
`function OnConfigure () {}
107+
function OnRequestHeaders(ctx) {}
108+
function OnResponseHeaders(ctx) {
109+
ctx.setResponseHeader("x-hello", "world");
110+
let status = ctx.getResponseHeader(":status");
111+
console.log("Response status: ", status);
112+
}`, logout)
113+
require.NoError(t, err)
114+
115+
f := &javaScriptFilter{vm: vm, responseHeaders: map[string]string{
116+
":status": "200",
117+
}}
118+
called := false
119+
m := &mockEnvoyHttpFilter{
120+
getResponseHeaders: func() map[string][]string { return map[string][]string{":status": {"200"}} },
121+
setResponseHeader: func(key string, value []byte) bool {
122+
require.Equal(t, "x-hello", key)
123+
require.Equal(t, "world", string(value))
124+
called = true
125+
return true
126+
},
127+
}
128+
129+
status := f.ResponseHeaders(m, false)
130+
require.Equal(t, gosdk.ResponseHeadersStatusContinue, status)
131+
require.True(t, called)
132+
133+
require.Contains(t, logout.String(), "Response status: 200")
134+
}

go/mock_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package main
2+
3+
import (
4+
"io"
5+
6+
"github.com/envoyproxy/dynamic-modules-examples/go/gosdk"
7+
)
8+
9+
// mockEnvoyHttpFilter is a mock implementation of [gosdk.EnvoyHttpFilter] for testing.
10+
type mockEnvoyHttpFilter struct {
11+
getRequestHeader func(key string) (string, bool)
12+
getRequestHeaders func() map[string][]string
13+
setRequestHeader func(key string, value []byte) bool
14+
getResponseHeader func(key string) (string, bool)
15+
getResponseHeaders func() map[string][]string
16+
setResponseHeader func(key string, value []byte) bool
17+
getRequestBody func() (io.Reader, bool)
18+
drainRequestBody func(n int) bool
19+
appendRequestBody func(data []byte) bool
20+
getResponseBody func() (io.Reader, bool)
21+
drainResponseBody func(n int) bool
22+
appendResponseBody func(data []byte) bool
23+
sendLocalReply func(statusCode uint32, headers [][2]string, body []byte)
24+
getSourceAddress func() string
25+
getRequestProtocol func() string
26+
newScheduler func() gosdk.Scheduler
27+
continueRequest func()
28+
continueResponse func()
29+
}
30+
31+
// GetRequestHeader implements [gosdk.EnvoyHttpFilter.GetRequestHeader].
32+
func (m mockEnvoyHttpFilter) GetRequestHeader(key string) (string, bool) {
33+
return m.getRequestHeader(key)
34+
}
35+
36+
// GetRequestHeaders implements [gosdk.EnvoyHttpFilter.GetRequestHeaders].
37+
func (m mockEnvoyHttpFilter) GetRequestHeaders() map[string][]string {
38+
return m.getRequestHeaders()
39+
}
40+
41+
// SetRequestHeader implements [gosdk.EnvoyHttpFilter.SetRequestHeader].
42+
func (m mockEnvoyHttpFilter) SetRequestHeader(key string, value []byte) bool {
43+
return m.setRequestHeader(key, value)
44+
}
45+
46+
// GetResponseHeader implements [gosdk.EnvoyHttpFilter.GetResponseHeader].
47+
func (m mockEnvoyHttpFilter) GetResponseHeader(key string) (string, bool) {
48+
return m.getResponseHeader(key)
49+
}
50+
51+
// GetResponseHeaders implements [gosdk.EnvoyHttpFilter.GetResponseHeaders].
52+
func (m mockEnvoyHttpFilter) GetResponseHeaders() map[string][]string {
53+
return m.getResponseHeaders()
54+
}
55+
56+
// SetResponseHeader implements [gosdk.EnvoyHttpFilter.SetResponseHeader].
57+
func (m mockEnvoyHttpFilter) SetResponseHeader(key string, value []byte) bool {
58+
return m.setResponseHeader(key, value)
59+
}
60+
61+
// GetRequestBody implements [gosdk.EnvoyHttpFilter.GetRequestBody].
62+
func (m mockEnvoyHttpFilter) GetRequestBody() (io.Reader, bool) {
63+
return m.getRequestBody()
64+
}
65+
66+
// DrainRequestBody implements [gosdk.EnvoyHttpFilter.DrainRequestBody].
67+
func (m mockEnvoyHttpFilter) DrainRequestBody(n int) bool {
68+
return m.drainRequestBody(n)
69+
}
70+
71+
// AppendRequestBody implements [gosdk.EnvoyHttpFilter.AppendRequestBody].
72+
func (m mockEnvoyHttpFilter) AppendRequestBody(data []byte) bool {
73+
return m.appendRequestBody(data)
74+
}
75+
76+
// GetResponseBody implements [gosdk.EnvoyHttpFilter.GetResponseBody].
77+
func (m mockEnvoyHttpFilter) GetResponseBody() (io.Reader, bool) {
78+
return m.getResponseBody()
79+
}
80+
81+
// DrainResponseBody implements [gosdk.EnvoyHttpFilter.DrainResponseBody].
82+
func (m mockEnvoyHttpFilter) DrainResponseBody(n int) bool {
83+
return m.drainResponseBody(n)
84+
}
85+
86+
// AppendResponseBody implements [gosdk.EnvoyHttpFilter.AppendResponseBody].
87+
func (m mockEnvoyHttpFilter) AppendResponseBody(data []byte) bool {
88+
return m.appendResponseBody(data)
89+
}
90+
91+
// SendLocalReply implements [gosdk.EnvoyHttpFilter.SendLocalReply].
92+
func (m mockEnvoyHttpFilter) SendLocalReply(statusCode uint32, headers [][2]string, body []byte) {
93+
m.sendLocalReply(statusCode, headers, body)
94+
}
95+
96+
// GetSourceAddress implements [gosdk.EnvoyHttpFilter.GetSourceAddress].
97+
func (m mockEnvoyHttpFilter) GetSourceAddress() string {
98+
return m.getSourceAddress()
99+
}
100+
101+
// GetRequestProtocol implements [gosdk.EnvoyHttpFilter.GetRequestProtocol].
102+
func (m mockEnvoyHttpFilter) GetRequestProtocol() string {
103+
return m.getRequestProtocol()
104+
}
105+
106+
// NewScheduler implements [gosdk.EnvoyHttpFilter.NewScheduler].
107+
func (m mockEnvoyHttpFilter) NewScheduler() gosdk.Scheduler {
108+
return m.newScheduler()
109+
}
110+
111+
// ContinueRequest implements [gosdk.EnvoyHttpFilter.ContinueRequest].
112+
func (m mockEnvoyHttpFilter) ContinueRequest() {
113+
m.continueRequest()
114+
}
115+
116+
// ContinueResponse implements [gosdk.EnvoyHttpFilter.ContinueResponse].
117+
func (m mockEnvoyHttpFilter) ContinueResponse() {
118+
m.continueResponse()
119+
}

0 commit comments

Comments
 (0)