github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/coprocess/grpc/coprocess_grpc_test.go (about) 1 // +build coprocess 2 // +build grpc 3 4 package grpc 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "io/ioutil" 10 "mime/multipart" 11 "net" 12 "net/http" 13 "os" 14 "strings" 15 "testing" 16 17 "context" 18 19 "google.golang.org/grpc" 20 21 "github.com/TykTechnologies/tyk/apidef" 22 "github.com/TykTechnologies/tyk/config" 23 "github.com/TykTechnologies/tyk/coprocess" 24 "github.com/TykTechnologies/tyk/gateway" 25 "github.com/TykTechnologies/tyk/test" 26 "github.com/TykTechnologies/tyk/user" 27 ) 28 29 const ( 30 grpcListenAddr = ":9999" 31 grpcListenPath = "tcp://127.0.0.1:9999" 32 33 testHeaderName = "Testheader" 34 testHeaderValue = "testvalue" 35 ) 36 37 type dispatcher struct{} 38 39 func (d *dispatcher) grpcError(object *coprocess.Object, errorMsg string) (*coprocess.Object, error) { 40 object.Request.ReturnOverrides.ResponseError = errorMsg 41 object.Request.ReturnOverrides.ResponseCode = 400 42 return object, nil 43 } 44 45 func (d *dispatcher) Dispatch(ctx context.Context, object *coprocess.Object) (*coprocess.Object, error) { 46 switch object.HookName { 47 case "testPreHook1": 48 object.Request.SetHeaders = map[string]string{ 49 testHeaderName: testHeaderValue, 50 } 51 case "testPreHook2": 52 contentType, found := object.Request.Headers["Content-Type"] 53 if !found { 54 return d.grpcError(object, "Content Type field not found") 55 } 56 if strings.Contains(contentType, "json") { 57 if len(object.Request.Body) == 0 { 58 return d.grpcError(object, "Body field is empty") 59 } 60 if len(object.Request.RawBody) == 0 { 61 return d.grpcError(object, "Raw body field is empty") 62 } 63 if strings.Compare(object.Request.Body, string(object.Request.Body)) != 0 { 64 return d.grpcError(object, "Raw body and body fields don't match") 65 } 66 } else if strings.Contains(contentType, "multipart") { 67 if len(object.Request.Body) != 0 { 68 return d.grpcError(object, "Body field isn't empty") 69 } 70 if len(object.Request.RawBody) == 0 { 71 return d.grpcError(object, "Raw body field is empty") 72 } 73 } else { 74 return d.grpcError(object, "Request content type should be either JSON or multipart") 75 } 76 case "testPostHook1": 77 testKeyValue, ok := object.Session.Metadata["testkey"] 78 if !ok { 79 return d.grpcError(object, "'testkey' not found in session metadata") 80 } 81 jsonObject := make(map[string]string) 82 if err := json.Unmarshal([]byte(testKeyValue), &jsonObject); err != nil { 83 return d.grpcError(object, "couldn't decode 'testkey' nested value") 84 } 85 nestedKeyValue, ok := jsonObject["nestedkey"] 86 if !ok { 87 return d.grpcError(object, "'nestedkey' not found in JSON object") 88 } 89 if nestedKeyValue != "nestedvalue" { 90 return d.grpcError(object, "'nestedvalue' value doesn't match") 91 } 92 testKey2Value, ok := object.Session.Metadata["testkey2"] 93 if !ok { 94 return d.grpcError(object, "'testkey' not found in session metadata") 95 } 96 if testKey2Value != "testvalue" { 97 return d.grpcError(object, "'testkey2' value doesn't match") 98 } 99 100 // Check for compatibility (object.Metadata should contain the same keys as object.Session.Metadata) 101 for k, v := range object.Metadata { 102 sessionKeyValue, ok := object.Session.Metadata[k] 103 if !ok { 104 return d.grpcError(object, k+" not found in object.Session.Metadata") 105 } 106 if strings.Compare(sessionKeyValue, v) != 0 { 107 return d.grpcError(object, k+" doesn't match value in object.Session.Metadata") 108 } 109 } 110 } 111 return object, nil 112 } 113 114 func (d *dispatcher) DispatchEvent(ctx context.Context, event *coprocess.Event) (*coprocess.EventReply, error) { 115 return &coprocess.EventReply{}, nil 116 } 117 118 func newTestGRPCServer() (s *grpc.Server) { 119 s = grpc.NewServer() 120 coprocess.RegisterDispatcherServer(s, &dispatcher{}) 121 return s 122 } 123 124 func loadTestGRPCAPIs() { 125 gateway.BuildAndLoadAPI(func(spec *gateway.APISpec) { 126 spec.APIID = "1" 127 spec.OrgID = gateway.MockOrgID 128 spec.Auth = apidef.Auth{ 129 AuthHeaderName: "authorization", 130 } 131 spec.UseKeylessAccess = false 132 spec.VersionData = struct { 133 NotVersioned bool `bson:"not_versioned" json:"not_versioned"` 134 DefaultVersion string `bson:"default_version" json:"default_version"` 135 Versions map[string]apidef.VersionInfo `bson:"versions" json:"versions"` 136 }{ 137 NotVersioned: true, 138 Versions: map[string]apidef.VersionInfo{ 139 "v1": { 140 Name: "v1", 141 }, 142 }, 143 } 144 spec.Proxy.ListenPath = "/grpc-test-api/" 145 spec.Proxy.StripListenPath = true 146 spec.CustomMiddleware = apidef.MiddlewareSection{ 147 Pre: []apidef.MiddlewareDefinition{ 148 {Name: "testPreHook1"}, 149 }, 150 Driver: apidef.GrpcDriver, 151 } 152 }, func(spec *gateway.APISpec) { 153 spec.APIID = "2" 154 spec.OrgID = gateway.MockOrgID 155 spec.Auth = apidef.Auth{ 156 AuthHeaderName: "authorization", 157 } 158 spec.UseKeylessAccess = true 159 spec.VersionData = struct { 160 NotVersioned bool `bson:"not_versioned" json:"not_versioned"` 161 DefaultVersion string `bson:"default_version" json:"default_version"` 162 Versions map[string]apidef.VersionInfo `bson:"versions" json:"versions"` 163 }{ 164 NotVersioned: true, 165 Versions: map[string]apidef.VersionInfo{ 166 "v1": { 167 Name: "v1", 168 }, 169 }, 170 } 171 spec.Proxy.ListenPath = "/grpc-test-api-2/" 172 spec.Proxy.StripListenPath = true 173 spec.CustomMiddleware = apidef.MiddlewareSection{ 174 Pre: []apidef.MiddlewareDefinition{ 175 {Name: "testPreHook2"}, 176 }, 177 Driver: apidef.GrpcDriver, 178 } 179 }, func(spec *gateway.APISpec) { 180 spec.APIID = "3" 181 spec.OrgID = "default" 182 spec.Auth = apidef.Auth{ 183 AuthHeaderName: "authorization", 184 } 185 spec.UseKeylessAccess = false 186 spec.VersionData = struct { 187 NotVersioned bool `bson:"not_versioned" json:"not_versioned"` 188 DefaultVersion string `bson:"default_version" json:"default_version"` 189 Versions map[string]apidef.VersionInfo `bson:"versions" json:"versions"` 190 }{ 191 NotVersioned: true, 192 Versions: map[string]apidef.VersionInfo{ 193 "v1": { 194 Name: "v1", 195 }, 196 }, 197 } 198 spec.Proxy.ListenPath = "/grpc-test-api-3/" 199 spec.Proxy.StripListenPath = true 200 spec.CustomMiddleware = apidef.MiddlewareSection{ 201 Post: []apidef.MiddlewareDefinition{ 202 {Name: "testPostHook1"}, 203 }, 204 Driver: apidef.GrpcDriver, 205 } 206 }) 207 } 208 209 func startTykWithGRPC() (*gateway.Test, *grpc.Server) { 210 // Setup the gRPC server: 211 listener, _ := net.Listen("tcp", grpcListenAddr) 212 grpcServer := newTestGRPCServer() 213 go grpcServer.Serve(listener) 214 215 // Setup Tyk: 216 cfg := config.CoProcessConfig{ 217 EnableCoProcess: true, 218 CoProcessGRPCServer: grpcListenPath, 219 } 220 ts := gateway.StartTest(gateway.TestConfig{CoprocessConfig: cfg}) 221 222 // Load test APIs: 223 loadTestGRPCAPIs() 224 return &ts, grpcServer 225 } 226 227 func TestMain(m *testing.M) { 228 os.Exit(gateway.InitTestMain(m)) 229 } 230 231 func TestGRPCDispatch(t *testing.T) { 232 ts, grpcServer := startTykWithGRPC() 233 defer ts.Close() 234 defer grpcServer.Stop() 235 236 keyID := gateway.CreateSession(func(s *user.SessionState) { 237 s.MetaData = map[string]interface{}{ 238 "testkey": map[string]interface{}{"nestedkey": "nestedvalue"}, 239 "testkey2": "testvalue", 240 } 241 }) 242 headers := map[string]string{"authorization": keyID} 243 244 t.Run("Pre Hook with SetHeaders", func(t *testing.T) { 245 res, err := ts.Run(t, test.TestCase{ 246 Path: "/grpc-test-api/", 247 Method: http.MethodGet, 248 Code: http.StatusOK, 249 Headers: headers, 250 }) 251 if err != nil { 252 t.Fatalf("Request failed: %s", err.Error()) 253 } 254 data, err := ioutil.ReadAll(res.Body) 255 if err != nil { 256 t.Fatalf("Couldn't read response body: %s", err.Error()) 257 } 258 var testResponse gateway.TestHttpResponse 259 err = json.Unmarshal(data, &testResponse) 260 if err != nil { 261 t.Fatalf("Couldn't unmarshal test response JSON: %s", err.Error()) 262 } 263 value, ok := testResponse.Headers[testHeaderName] 264 if !ok { 265 t.Fatalf("Header not found, expecting %s", testHeaderName) 266 } 267 if value != testHeaderValue { 268 t.Fatalf("Header value isn't %s", testHeaderValue) 269 } 270 }) 271 272 t.Run("Pre Hook with UTF-8/non-UTF-8 request data", func(t *testing.T) { 273 fileData := gateway.GenerateTestBinaryData() 274 var buf bytes.Buffer 275 multipartWriter := multipart.NewWriter(&buf) 276 file, err := multipartWriter.CreateFormFile("file", "test.bin") 277 if err != nil { 278 t.Fatalf("Couldn't use multipart writer: %s", err.Error()) 279 } 280 _, err = fileData.WriteTo(file) 281 if err != nil { 282 t.Fatalf("Couldn't write to multipart file: %s", err.Error()) 283 } 284 field, err := multipartWriter.CreateFormField("testfield") 285 if err != nil { 286 t.Fatalf("Couldn't use multipart writer: %s", err.Error()) 287 } 288 _, err = field.Write([]byte("testvalue")) 289 if err != nil { 290 t.Fatalf("Couldn't write to form field: %s", err.Error()) 291 } 292 err = multipartWriter.Close() 293 if err != nil { 294 t.Fatalf("Couldn't close multipart writer: %s", err.Error()) 295 } 296 297 ts.Run(t, []test.TestCase{ 298 {Path: "/grpc-test-api-2/", Code: 200, Data: &buf, Headers: map[string]string{"Content-Type": multipartWriter.FormDataContentType()}}, 299 {Path: "/grpc-test-api-2/", Code: 200, Data: "{}", Headers: map[string]string{"Content-Type": "application/json"}}, 300 }...) 301 }) 302 303 t.Run("Post Hook with metadata", func(t *testing.T) { 304 ts.Run(t, test.TestCase{ 305 Path: "/grpc-test-api-3/", 306 Method: http.MethodGet, 307 Code: http.StatusOK, 308 Headers: headers, 309 }) 310 }) 311 312 } 313 314 func BenchmarkGRPCDispatch(b *testing.B) { 315 ts, grpcServer := startTykWithGRPC() 316 defer ts.Close() 317 defer grpcServer.Stop() 318 319 keyID := gateway.CreateSession(func(s *user.SessionState) {}) 320 headers := map[string]string{"authorization": keyID} 321 322 b.Run("Pre Hook with SetHeaders", func(b *testing.B) { 323 path := "/grpc-test-api/" 324 b.ReportAllocs() 325 for i := 0; i < b.N; i++ { 326 ts.Run(b, test.TestCase{ 327 Path: path, 328 Method: http.MethodGet, 329 Code: http.StatusOK, 330 Headers: headers, 331 }) 332 } 333 }) 334 }