github.com/cloudwego/kitex@v0.9.0/pkg/generic/thriftidl_provider.go (about) 1 /* 2 * Copyright 2021 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package generic 18 19 import ( 20 "context" 21 "fmt" 22 "path/filepath" 23 "sync" 24 25 dthrift "github.com/cloudwego/dynamicgo/thrift" 26 "github.com/cloudwego/thriftgo/parser" 27 28 "github.com/cloudwego/kitex/pkg/generic/descriptor" 29 "github.com/cloudwego/kitex/pkg/generic/thrift" 30 "github.com/cloudwego/kitex/pkg/klog" 31 ) 32 33 var ( 34 _ Closer = &ThriftContentProvider{} 35 _ Closer = &ThriftContentWithAbsIncludePathProvider{} 36 ) 37 38 type thriftFileProvider struct { 39 closeOnce sync.Once 40 svcs chan *descriptor.ServiceDescriptor 41 opts *ProviderOption 42 } 43 44 // NewThriftFileProvider create a ThriftIDLProvider by given path and include dirs 45 func NewThriftFileProvider(path string, includeDirs ...string) (DescriptorProvider, error) { 46 p := &thriftFileProvider{ 47 svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel 48 opts: &ProviderOption{DynamicGoEnabled: false}, 49 } 50 svc, err := newServiceDescriptorFromPath(path, includeDirs...) 51 if err != nil { 52 return nil, err 53 } 54 p.svcs <- svc 55 return p, nil 56 } 57 58 // NewThriftFileProviderWithDynamicGo create a ThriftIDLProvider with dynamicgo by given path and include dirs 59 func NewThriftFileProviderWithDynamicGo(path string, includeDirs ...string) (DescriptorProvider, error) { 60 p := &thriftFileProvider{ 61 svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel 62 opts: &ProviderOption{DynamicGoEnabled: true}, 63 } 64 65 svc, err := newServiceDescriptorFromPath(path, includeDirs...) 66 if err != nil { 67 return nil, err 68 } 69 70 // ServiceDescriptor of dynamicgo 71 dOpts := dthrift.Options{EnableThriftBase: true} 72 dsvc, err := dOpts.NewDescritorFromPath(context.Background(), path, includeDirs...) 73 if err != nil { 74 // fall back to the original way (without dynamicgo) 75 p.opts.DynamicGoEnabled = false 76 p.svcs <- svc 77 klog.CtxWarnf(context.Background(), "KITEX: failed to get dynamicgo service descriptor, fall back to the original way, error=%s", err) 78 return p, nil 79 } 80 svc.DynamicGoDsc = dsvc 81 82 p.svcs <- svc 83 return p, nil 84 } 85 86 func newServiceDescriptorFromPath(path string, includeDirs ...string) (*descriptor.ServiceDescriptor, error) { 87 tree, err := parser.ParseFile(path, includeDirs, true) 88 if err != nil { 89 return nil, err 90 } 91 svc, err := thrift.Parse(tree, thrift.DefaultParseMode()) 92 if err != nil { 93 return nil, err 94 } 95 return svc, nil 96 } 97 98 // maybe watch the file change and reparse 99 // p.thrifts <- some change 100 // func (p *thriftFileProvider) watchAndUpdate() {} 101 102 func (p *thriftFileProvider) Provide() <-chan *descriptor.ServiceDescriptor { 103 return p.svcs 104 } 105 106 // Close the sending chan. 107 func (p *thriftFileProvider) Close() error { 108 p.closeOnce.Do(func() { 109 close(p.svcs) 110 }) 111 return nil 112 } 113 114 func (p *thriftFileProvider) Option() ProviderOption { 115 return *p.opts 116 } 117 118 // ThriftContentProvider provide descriptor from contents 119 type ThriftContentProvider struct { 120 closeOnce sync.Once 121 svcs chan *descriptor.ServiceDescriptor 122 opts *ProviderOption 123 } 124 125 var _ DescriptorProvider = (*ThriftContentProvider)(nil) 126 127 const defaultMainIDLPath = "main.thrift" 128 129 // NewThriftContentProvider builder 130 func NewThriftContentProvider(main string, includes map[string]string) (*ThriftContentProvider, error) { 131 p := &ThriftContentProvider{ 132 svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel 133 opts: &ProviderOption{DynamicGoEnabled: false}, 134 } 135 svc, err := newServiceDescriptorFromContent(defaultMainIDLPath, main, includes, false) 136 if err != nil { 137 return nil, err 138 } 139 140 p.svcs <- svc 141 return p, nil 142 } 143 144 // NewThriftContentProviderWithDynamicGo builder 145 func NewThriftContentProviderWithDynamicGo(main string, includes map[string]string) (*ThriftContentProvider, error) { 146 p := &ThriftContentProvider{ 147 svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel 148 opts: &ProviderOption{DynamicGoEnabled: true}, 149 } 150 svc, err := newServiceDescriptorFromContent(defaultMainIDLPath, main, includes, false) 151 if err != nil { 152 return nil, err 153 } 154 155 p.newDynamicGoDsc(svc, defaultMainIDLPath, main, includes) 156 157 p.svcs <- svc 158 return p, nil 159 } 160 161 // UpdateIDL ... 162 func (p *ThriftContentProvider) UpdateIDL(main string, includes map[string]string) error { 163 var svc *descriptor.ServiceDescriptor 164 tree, err := ParseContent(defaultMainIDLPath, main, includes, false) 165 if err != nil { 166 return err 167 } 168 svc, err = thrift.Parse(tree, thrift.DefaultParseMode()) 169 if err != nil { 170 return err 171 } 172 173 if p.opts.DynamicGoEnabled { 174 p.newDynamicGoDsc(svc, defaultMainIDLPath, main, includes) 175 } 176 177 select { 178 case <-p.svcs: 179 default: 180 } 181 select { 182 case p.svcs <- svc: 183 default: 184 } 185 return nil 186 } 187 188 // Provide ... 189 func (p *ThriftContentProvider) Provide() <-chan *descriptor.ServiceDescriptor { 190 return p.svcs 191 } 192 193 // Close the sending chan. 194 func (p *ThriftContentProvider) Close() error { 195 p.closeOnce.Do(func() { 196 close(p.svcs) 197 }) 198 return nil 199 } 200 201 // Option ... 202 func (p *ThriftContentProvider) Option() ProviderOption { 203 return *p.opts 204 } 205 206 func (p *ThriftContentProvider) newDynamicGoDsc(svc *descriptor.ServiceDescriptor, path, content string, includes map[string]string) { 207 if err := newDynamicGoDscFromContent(svc, path, content, includes, false); err != nil { 208 p.opts.DynamicGoEnabled = false 209 } 210 } 211 212 func parseIncludes(tree *parser.Thrift, parsed map[string]*parser.Thrift, sources map[string]string, isAbsIncludePath bool) (err error) { 213 for _, i := range tree.Includes { 214 p := i.Path 215 if isAbsIncludePath { 216 p = absPath(tree.Filename, i.Path) 217 } 218 ref, ok := parsed[p] // avoid infinite recursion 219 if ok { 220 i.Reference = ref 221 continue 222 } 223 if src, ok := sources[p]; !ok { 224 return fmt.Errorf("miss include path: %s for file: %s", p, tree.Filename) 225 } else { 226 if ref, err = parser.ParseString(p, src); err != nil { 227 return 228 } 229 } 230 parsed[p] = ref 231 i.Reference = ref 232 if err = parseIncludes(ref, parsed, sources, isAbsIncludePath); err != nil { 233 return 234 } 235 } 236 return nil 237 } 238 239 // path := /a/b/c.thrift 240 // includePath := ../d.thrift 241 // result := /a/d.thrift 242 func absPath(path, includePath string) string { 243 if filepath.IsAbs(includePath) { 244 return includePath 245 } 246 return filepath.Join(filepath.Dir(path), includePath) 247 } 248 249 // ParseContent parses the IDL from path and content using provided includes 250 func ParseContent(path, content string, includes map[string]string, isAbsIncludePath bool) (*parser.Thrift, error) { 251 if src := includes[path]; src != "" && src != content { 252 return nil, fmt.Errorf("provided main IDL content conflicts with includes: %q", path) 253 } 254 tree, err := parser.ParseString(path, content) 255 if err != nil { 256 return nil, err 257 } 258 parsed := make(map[string]*parser.Thrift) 259 parsed[path] = tree 260 if err := parseIncludes(tree, parsed, includes, isAbsIncludePath); err != nil { 261 return nil, err 262 } 263 if cir := parser.CircleDetect(tree); cir != "" { 264 return tree, fmt.Errorf("IDL circular dependency: %s", cir) 265 } 266 return tree, nil 267 } 268 269 // ThriftContentWithAbsIncludePathProvider ... 270 type ThriftContentWithAbsIncludePathProvider struct { 271 closeOnce sync.Once 272 svcs chan *descriptor.ServiceDescriptor 273 opts *ProviderOption 274 } 275 276 var _ DescriptorProvider = (*ThriftContentWithAbsIncludePathProvider)(nil) 277 278 // NewThriftContentWithAbsIncludePathProvider create abs include path DescriptorProvider 279 func NewThriftContentWithAbsIncludePathProvider(mainIDLPath string, includes map[string]string) (*ThriftContentWithAbsIncludePathProvider, error) { 280 p := &ThriftContentWithAbsIncludePathProvider{ 281 svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel 282 opts: &ProviderOption{DynamicGoEnabled: false}, 283 } 284 mainIDLContent, ok := includes[mainIDLPath] 285 if !ok { 286 return nil, fmt.Errorf("miss main IDL content for main IDL path: %s", mainIDLPath) 287 } 288 svc, err := newServiceDescriptorFromContent(mainIDLPath, mainIDLContent, includes, true) 289 if err != nil { 290 return nil, err 291 } 292 293 p.svcs <- svc 294 return p, nil 295 } 296 297 // NewThriftContentWithAbsIncludePathProviderWithDynamicGo create abs include path DescriptorProvider with dynamicgo 298 func NewThriftContentWithAbsIncludePathProviderWithDynamicGo(mainIDLPath string, includes map[string]string) (*ThriftContentWithAbsIncludePathProvider, error) { 299 p := &ThriftContentWithAbsIncludePathProvider{ 300 svcs: make(chan *descriptor.ServiceDescriptor, 1), // unblock with buffered channel 301 opts: &ProviderOption{DynamicGoEnabled: true}, 302 } 303 mainIDLContent, ok := includes[mainIDLPath] 304 if !ok { 305 return nil, fmt.Errorf("miss main IDL content for main IDL path: %s", mainIDLPath) 306 } 307 svc, err := newServiceDescriptorFromContent(mainIDLPath, mainIDLContent, includes, true) 308 if err != nil { 309 return nil, err 310 } 311 312 p.newDynamicGoDsc(svc, mainIDLPath, mainIDLContent, includes) 313 314 p.svcs <- svc 315 return p, nil 316 } 317 318 // UpdateIDL update idl by given args 319 func (p *ThriftContentWithAbsIncludePathProvider) UpdateIDL(mainIDLPath string, includes map[string]string) error { 320 mainIDLContent, ok := includes[mainIDLPath] 321 if !ok { 322 return fmt.Errorf("miss main IDL content for main IDL path: %s", mainIDLPath) 323 } 324 var svc *descriptor.ServiceDescriptor 325 tree, err := ParseContent(mainIDLPath, mainIDLContent, includes, true) 326 if err != nil { 327 return err 328 } 329 svc, err = thrift.Parse(tree, thrift.DefaultParseMode()) 330 if err != nil { 331 return err 332 } 333 334 if p.opts.DynamicGoEnabled { 335 p.newDynamicGoDsc(svc, mainIDLPath, mainIDLContent, includes) 336 } 337 338 // drain the channel 339 select { 340 case <-p.svcs: 341 default: 342 } 343 select { 344 case p.svcs <- svc: 345 default: 346 } 347 return nil 348 } 349 350 // Provide ... 351 func (p *ThriftContentWithAbsIncludePathProvider) Provide() <-chan *descriptor.ServiceDescriptor { 352 return p.svcs 353 } 354 355 // Close the sending chan. 356 func (p *ThriftContentWithAbsIncludePathProvider) Close() error { 357 p.closeOnce.Do(func() { 358 close(p.svcs) 359 }) 360 return nil 361 } 362 363 // Option ... 364 func (p *ThriftContentWithAbsIncludePathProvider) Option() ProviderOption { 365 return *p.opts 366 } 367 368 func (p *ThriftContentWithAbsIncludePathProvider) newDynamicGoDsc(svc *descriptor.ServiceDescriptor, path, content string, includes map[string]string) { 369 if err := newDynamicGoDscFromContent(svc, path, content, includes, true); err != nil { 370 p.opts.DynamicGoEnabled = false 371 } 372 } 373 374 func newServiceDescriptorFromContent(path, content string, includes map[string]string, isAbsIncludePath bool) (*descriptor.ServiceDescriptor, error) { 375 tree, err := ParseContent(path, content, includes, isAbsIncludePath) 376 if err != nil { 377 return nil, err 378 } 379 svc, err := thrift.Parse(tree, thrift.DefaultParseMode()) 380 if err != nil { 381 return nil, err 382 } 383 return svc, nil 384 } 385 386 func newDynamicGoDscFromContent(svc *descriptor.ServiceDescriptor, path, content string, includes map[string]string, isAbsIncludePath bool) error { 387 // ServiceDescriptor of dynamicgo 388 dOpts := dthrift.Options{EnableThriftBase: true} 389 dsvc, err := dOpts.NewDescritorFromContent(context.Background(), path, content, includes, isAbsIncludePath) 390 if err != nil { 391 klog.CtxWarnf(context.Background(), "KITEX: failed to get dynamicgo service descriptor, fall back to the original way, error=%s", err) 392 return err 393 } 394 svc.DynamicGoDsc = dsvc 395 return nil 396 }