From 32065d46c874ac176c4960c3fcec158f24a921ba Mon Sep 17 00:00:00 2001 From: Mike Cutalo Date: Mon, 20 May 2024 13:14:13 -0700 Subject: [PATCH] gateway: routable static base path (#3010) --- api/config/gateway/v1/gateway.proto | 5 + backend/api/config/gateway/v1/gateway.pb.go | 108 ++++++++++-------- .../config/gateway/v1/gateway.pb.validate.go | 2 + backend/clutch-config.yaml | 2 + backend/gateway/mux/mux.go | 30 ++++- backend/gateway/mux/mux_test.go | 64 +++++++++++ frontend/api/src/index.d.ts | 6 + frontend/api/src/index.js | 18 +++ 8 files changed, 183 insertions(+), 52 deletions(-) diff --git a/api/config/gateway/v1/gateway.proto b/api/config/gateway/v1/gateway.proto index 3d90f9c403..c05dea6b4e 100644 --- a/api/config/gateway/v1/gateway.proto +++ b/api/config/gateway/v1/gateway.proto @@ -133,6 +133,11 @@ message Assets { oneof provider { S3Provider s3 = 1; } + + // Clutch hosts static assets on the `/static/` path, any request to this path will result in a lookup of static + // assets. However if you would like to utilize this route for other pruposes in additon to static assets, you can + // enable this feature. eg: clutch.sh/static || /static/* -> you can now handle this route as you wish. + bool routable_static_path = 2; } message Logger { diff --git a/backend/api/config/gateway/v1/gateway.pb.go b/backend/api/config/gateway/v1/gateway.pb.go index cca990f61f..eadb2ddc15 100644 --- a/backend/api/config/gateway/v1/gateway.pb.go +++ b/backend/api/config/gateway/v1/gateway.pb.go @@ -616,6 +616,10 @@ type Assets struct { // // *Assets_S3 Provider isAssets_Provider `protobuf_oneof:"provider"` + // Clutch hosts static assets on the `/static/` path, any request to this path will result in a lookup of static + // assets. However if you would like to utilize this route for other pruposes in additon to static assets, you can + // enable this feature. eg: clutch.sh/static || /static/* -> you can now handle this route as you wish. + RoutableStaticPath bool `protobuf:"varint,2,opt,name=routable_static_path,json=routableStaticPath,proto3" json:"routable_static_path,omitempty"` } func (x *Assets) Reset() { @@ -664,6 +668,13 @@ func (x *Assets) GetS3() *Assets_S3Provider { return nil } +func (x *Assets) GetRoutableStaticPath() bool { + if x != nil { + return x.RoutableStaticPath + } + return false +} + type isAssets_Provider interface { isAssets_Provider() } @@ -1528,61 +1539,64 @@ var file_config_gateway_v1_gateway_proto_rawDesc = []byte{ 0x5f, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0d, 0x73, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x73, 0x22, 0xa3, 0x01, 0x0a, 0x06, 0x41, 0x73, + 0x72, 0x65, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x73, 0x22, 0xd5, 0x01, 0x0a, 0x06, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x12, 0x3d, 0x0a, 0x02, 0x73, 0x33, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x6c, 0x75, 0x74, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2e, 0x53, 0x33, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, - 0x02, 0x73, 0x33, 0x1a, 0x4e, 0x0a, 0x0a, 0x53, 0x33, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x63, - 0x6b, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, - 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x22, - 0xe2, 0x01, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x12, 0x3c, 0x0a, 0x05, 0x6c, 0x65, - 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x63, 0x6c, 0x75, 0x74, - 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x2e, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x74, - 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x70, 0x72, 0x65, 0x74, - 0x74, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x22, 0x58, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, - 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, - 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x05, 0x12, - 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x06, 0x42, 0x08, 0x0a, 0x06, 0x66, 0x6f, - 0x72, 0x6d, 0x61, 0x74, 0x22, 0x62, 0x0a, 0x0a, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, - 0x72, 0x65, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x20, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x37, 0x0a, 0x0c, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x0b, 0x74, 0x79, 0x70, - 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x5f, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x20, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x0b, 0x74, 0x79, - 0x70, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x60, 0x0a, 0x08, 0x52, 0x65, 0x73, - 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x20, 0x01, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x0b, - 0x74, 0x79, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x5e, 0x0a, 0x06, 0x4d, - 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x02, 0x73, 0x33, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x6f, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x12, 0x72, 0x6f, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, + 0x63, 0x50, 0x61, 0x74, 0x68, 0x1a, 0x4e, 0x0a, 0x0a, 0x53, 0x33, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x62, + 0x75, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x63, + 0x6b, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x22, 0xe2, 0x01, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x12, 0x3c, 0x0a, 0x05, + 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x63, 0x6c, + 0x75, 0x74, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x67, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x2e, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x06, 0x70, 0x72, + 0x65, 0x74, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x70, 0x72, + 0x65, 0x74, 0x74, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x22, 0x58, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0f, 0x0a, 0x0b, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, + 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, + 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, + 0x05, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x06, 0x42, 0x08, 0x0a, 0x06, + 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22, 0x62, 0x0a, 0x0a, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, + 0x77, 0x61, 0x72, 0x65, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x20, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x0b, 0x74, + 0x79, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x5f, 0x0a, 0x07, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x20, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x0b, - 0x74, 0x79, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x40, 0x5a, 0x3e, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x79, 0x66, 0x74, 0x2f, 0x63, - 0x6c, 0x75, 0x74, 0x63, 0x68, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x76, 0x31, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x79, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x60, 0x0a, 0x08, 0x52, + 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x20, 0x01, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, + 0x52, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x5e, 0x0a, + 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x20, 0x01, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x0c, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, + 0x52, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x40, 0x5a, + 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x79, 0x66, 0x74, + 0x2f, 0x63, 0x6c, 0x75, 0x74, 0x63, 0x68, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x67, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x2f, 0x76, 0x31, 0x3b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/backend/api/config/gateway/v1/gateway.pb.validate.go b/backend/api/config/gateway/v1/gateway.pb.validate.go index 26720164b3..52e464aca9 100644 --- a/backend/api/config/gateway/v1/gateway.pb.validate.go +++ b/backend/api/config/gateway/v1/gateway.pb.validate.go @@ -1440,6 +1440,8 @@ func (m *Assets) validate(all bool) error { var errors []error + // no validation rules for RoutableStaticPath + switch v := m.Provider.(type) { case *Assets_S3: if v == nil { diff --git a/backend/clutch-config.yaml b/backend/clutch-config.yaml index 3954e2e4c9..4a95786854 100644 --- a/backend/clutch-config.yaml +++ b/backend/clutch-config.yaml @@ -2,6 +2,8 @@ gateway: logger: pretty: true level: DEBUG + assets: + routableStaticPath: true accesslog: # log http 5xx errors by default status_code_filters: diff --git a/backend/gateway/mux/mux.go b/backend/gateway/mux/mux.go index 31fdf009d8..9da683fd64 100644 --- a/backend/gateway/mux/mux.go +++ b/backend/gateway/mux/mux.go @@ -30,12 +30,16 @@ import ( ) const ( - xHeader = "X-" - xForwardedFor = "X-Forwarded-For" - xForwardedHost = "X-Forwarded-Host" + xHeader = "X-" + xForwardedFor = "X-Forwarded-For" + xForwardedHost = "X-Forwarded-Host" + staticAssetPath = "/static/" ) -var apiPattern = regexp.MustCompile(`^/v\d+/`) +var ( + apiPattern = regexp.MustCompile(`^/v\d+/`) + staticRoutePattern = regexp.MustCompile(`^/static*`) +) type assetHandler struct { assetCfg *gatewayv1.Assets @@ -76,10 +80,17 @@ func (a *assetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Set the original path. r.URL.Path = origPath + // if enableStaticBaseRoute is set to true, we wont attempt to serve assets if there is no extension in the path. + // This is to prevent serving the SPA when the user is trying to access a nested route. + if a.isStaticPathRoutable(r.URL.Path) { + r.URL.Path = "/" + a.fileServer.ServeHTTP(w, r) + } + // Serve! if f, err := a.fileSystem.Open(r.URL.Path); err != nil { // If not a known static asset and an asset provider is configured, try streaming from the configured provider. - if a.assetCfg != nil && a.assetCfg.Provider != nil && strings.HasPrefix(r.URL.Path, "/static/") { + if a.assetCfg != nil && a.assetCfg.Provider != nil && strings.HasPrefix(r.URL.Path, staticAssetPath) { // We attach this header simply for observability purposes. // Otherwise its difficult to know if the assets are being served from the configured provider. w.Header().Set("x-clutch-asset-passthrough", "true") @@ -110,6 +121,15 @@ func (a *assetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { a.fileServer.ServeHTTP(w, r) } +func (a *assetHandler) isStaticPathRoutable(urlPath string) bool { + if a.assetCfg != nil && a.assetCfg.RoutableStaticPath { + // If the path is the base route, we need to serve the SPA. + return staticRoutePattern.MatchString(urlPath) && path.Ext(urlPath) == "" && urlPath != "/" + } + + return false +} + func (a *assetHandler) assetProviderHandler(ctx context.Context, urlPath string) (io.ReadCloser, error) { switch a.assetCfg.Provider.(type) { case *gatewayv1.Assets_S3: diff --git a/backend/gateway/mux/mux_test.go b/backend/gateway/mux/mux_test.go index e332c4cf78..e038113fe4 100644 --- a/backend/gateway/mux/mux_test.go +++ b/backend/gateway/mux/mux_test.go @@ -235,3 +235,67 @@ func TestCustomResponseForwarderAuthCookiesNonBrowser(t *testing.T) { assert.Equal(t, 200, rec.Code) assert.Equal(t, "", rec.Header().Get("Location")) } + +func TestRoutableStaticPathEnabled(t *testing.T) { + assetHandler := &assetHandler{ + assetCfg: &gatewayv1.Assets{ + RoutableStaticPath: true, + }, + } + + testCases := []struct { + id string + urlPath string + expected bool + }{ + { + id: "should not route /", + urlPath: "/", + expected: false, + }, + { + id: "should route static assets", + urlPath: "/static/main.js", + expected: false, + }, + { + id: "should route static assets", + urlPath: "/static/main.css", + expected: false, + }, + { + id: "should serve the base route", + urlPath: "/static", + expected: true, + }, + { + id: "should serve the base route with a trailing slash", + urlPath: "/static/", + expected: true, + }, + { + id: "should serve the base route with longer paths", + urlPath: "/static/hello", + expected: true, + }, + { + id: "should serve the base route with query params", + urlPath: "/static/hello?foo=bar", + expected: true, + }, + { + id: "should not route apis", + urlPath: "/v1/getstatic/hello", + expected: false, + }, + { + id: "should not route apis", + urlPath: "/v1/staticapi/hello", + expected: false, + }, + } + + for _, test := range testCases { + assert.Equal(t, test.expected, assetHandler.isStaticPathRoutable(test.urlPath), test.id) + } +} diff --git a/frontend/api/src/index.d.ts b/frontend/api/src/index.d.ts index 4965b01acc..7e72164df4 100644 --- a/frontend/api/src/index.d.ts +++ b/frontend/api/src/index.d.ts @@ -7253,6 +7253,9 @@ export namespace clutch { /** Assets s3 */ s3?: (clutch.config.gateway.v1.Assets.IS3Provider|null); + + /** Assets routableStaticPath */ + routableStaticPath?: (boolean|null); } /** Represents an Assets. */ @@ -7267,6 +7270,9 @@ export namespace clutch { /** Assets s3. */ public s3?: (clutch.config.gateway.v1.Assets.IS3Provider|null); + /** Assets routableStaticPath. */ + public routableStaticPath: boolean; + /** Assets provider. */ public provider?: "s3"; diff --git a/frontend/api/src/index.js b/frontend/api/src/index.js index bc4886ed5a..f832c105cd 100644 --- a/frontend/api/src/index.js +++ b/frontend/api/src/index.js @@ -17348,6 +17348,7 @@ export const clutch = $root.clutch = (() => { * @memberof clutch.config.gateway.v1 * @interface IAssets * @property {clutch.config.gateway.v1.Assets.IS3Provider|null} [s3] Assets s3 + * @property {boolean|null} [routableStaticPath] Assets routableStaticPath */ /** @@ -17373,6 +17374,14 @@ export const clutch = $root.clutch = (() => { */ Assets.prototype.s3 = null; + /** + * Assets routableStaticPath. + * @member {boolean} routableStaticPath + * @memberof clutch.config.gateway.v1.Assets + * @instance + */ + Assets.prototype.routableStaticPath = false; + // OneOf field names bound to virtual getters and setters let $oneOfFields; @@ -17407,6 +17416,9 @@ export const clutch = $root.clutch = (() => { return "s3." + error; } } + if (message.routableStaticPath != null && message.hasOwnProperty("routableStaticPath")) + if (typeof message.routableStaticPath !== "boolean") + return "routableStaticPath: boolean expected"; return null; }; @@ -17427,6 +17439,8 @@ export const clutch = $root.clutch = (() => { throw TypeError(".clutch.config.gateway.v1.Assets.s3: object expected"); message.s3 = $root.clutch.config.gateway.v1.Assets.S3Provider.fromObject(object.s3); } + if (object.routableStaticPath != null) + message.routableStaticPath = Boolean(object.routableStaticPath); return message; }; @@ -17443,11 +17457,15 @@ export const clutch = $root.clutch = (() => { if (!options) options = {}; let object = {}; + if (options.defaults) + object.routableStaticPath = false; if (message.s3 != null && message.hasOwnProperty("s3")) { object.s3 = $root.clutch.config.gateway.v1.Assets.S3Provider.toObject(message.s3, options); if (options.oneofs) object.provider = "s3"; } + if (message.routableStaticPath != null && message.hasOwnProperty("routableStaticPath")) + object.routableStaticPath = message.routableStaticPath; return object; };