github.com/avenga/couper@v1.12.2/config/runtime/endpoint.go (about) 1 package runtime 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/sirupsen/logrus" 9 10 "github.com/avenga/couper/cache" 11 "github.com/avenga/couper/config" 12 "github.com/avenga/couper/config/runtime/server" 13 "github.com/avenga/couper/config/sequence" 14 "github.com/avenga/couper/errors" 15 "github.com/avenga/couper/eval/buffer" 16 "github.com/avenga/couper/handler" 17 "github.com/avenga/couper/handler/producer" 18 ) 19 20 func newEndpointMap(srvConf *config.Server, serverOptions *server.Options) (endpointMap, error) { 21 endpoints := make(endpointMap) 22 23 catchAllEndpoints := make(map[string]struct{}) 24 for _, apiConf := range srvConf.APIs { 25 basePath := serverOptions.APIBasePaths[apiConf] 26 for _, epConf := range apiConf.Endpoints { 27 endpoints[epConf] = apiConf 28 if epConf.Pattern == "/**" { 29 catchAllEndpoints[basePath] = struct{}{} 30 } 31 } 32 } 33 34 for _, apiConf := range srvConf.APIs { 35 basePath := serverOptions.APIBasePaths[apiConf] 36 if _, exists := catchAllEndpoints[basePath]; exists { 37 continue 38 } 39 40 if len(newAC(srvConf, apiConf).List()) == 0 { 41 continue 42 } 43 44 var ( 45 spaPaths []string 46 filesPaths []string 47 isAPIBasePathUniqueToFilesAndSPAs = true 48 ) 49 50 if len(serverOptions.SPABasePaths) == 0 { 51 spaPaths = []string{""} 52 } else { 53 spaPaths = serverOptions.SPABasePaths 54 } 55 56 if len(serverOptions.FilesBasePaths) == 0 { 57 filesPaths = []string{""} 58 } else { 59 filesPaths = serverOptions.FilesBasePaths 60 } 61 62 uniquePaths: 63 for _, spaPath := range spaPaths { 64 for _, filesPath := range filesPaths { 65 isAPIBasePathUniqueToFilesAndSPAs = basePath != filesPath && basePath != spaPath 66 67 if !isAPIBasePathUniqueToFilesAndSPAs { 68 break uniquePaths 69 } 70 } 71 } 72 73 if isAPIBasePathUniqueToFilesAndSPAs { 74 endpoints[apiConf.CatchAllEndpoint] = apiConf 75 catchAllEndpoints[basePath] = struct{}{} 76 } 77 } 78 79 for _, epConf := range srvConf.Endpoints { 80 endpoints[epConf] = nil 81 } 82 83 return endpoints, nil 84 } 85 86 func NewEndpointOptions(confCtx *hcl.EvalContext, endpointConf *config.Endpoint, apiConf *config.API, 87 serverOptions *server.Options, log *logrus.Entry, conf *config.Couper, memStore *cache.MemoryStore) (*handler.EndpointOptions, error) { 88 var errTpl *errors.Template 89 90 if endpointConf.ErrorFile != "" { 91 tpl, err := errors.NewTemplateFromFile(endpointConf.ErrorFile, log) 92 if err != nil { 93 return nil, err 94 } 95 errTpl = tpl 96 } else if apiConf != nil { 97 errTpl = serverOptions.APIErrTpls[apiConf] 98 } else { 99 errTpl = serverOptions.ServerErrTpl 100 } 101 102 // blockBodies contains inner endpoint block remain bodies to determine req/res buffer options. 103 var blockBodies []hcl.Body 104 105 var response *producer.Response 106 // var redirect producer.Redirect // TODO: configure redirect block 107 108 if endpointConf.Response != nil { 109 response = &producer.Response{ 110 Context: endpointConf.Response.HCLBody(), 111 } 112 blockBodies = append(blockBodies, response.Context) 113 } 114 115 allProducers := make(map[string]producer.Roundtrip) 116 for _, proxyConf := range endpointConf.Proxies { 117 backend, berr := NewBackend(confCtx, proxyConf.Backend, log, conf, memStore) 118 if berr != nil { 119 return nil, berr 120 } 121 122 var hasWSblock bool 123 proxyBody := proxyConf.HCLBody() 124 for _, b := range proxyBody.Blocks { 125 if b.Type == "websockets" { 126 hasWSblock = true 127 break 128 } 129 } 130 131 allowWebsockets := proxyConf.Websockets != nil || hasWSblock 132 proxyHandler := handler.NewProxy(backend, proxyBody, allowWebsockets, log) 133 134 p := &producer.Proxy{ 135 Content: proxyBody, 136 Name: proxyConf.Name, 137 RoundTrip: proxyHandler, 138 } 139 140 allProducers[proxyConf.Name] = p 141 blockBodies = append(blockBodies, proxyConf.Backend, proxyBody) 142 } 143 144 for _, requestConf := range endpointConf.Requests { 145 backend, berr := NewBackend(confCtx, requestConf.Backend, log, conf, memStore) 146 if berr != nil { 147 return nil, berr 148 } 149 150 pr := &producer.Request{ 151 Backend: backend, 152 Context: requestConf.HCLBody(), 153 Name: requestConf.Name, 154 } 155 156 allProducers[requestConf.Name] = pr 157 blockBodies = append(blockBodies, requestConf.Backend, requestConf.HCLBody()) 158 } 159 160 markDependencies(allProducers, endpointConf.Sequences) 161 addIndependentProducers(allProducers, endpointConf) 162 163 // TODO: redirect 164 if endpointConf.Response == nil && len(allProducers) == 0 { // && redirect == nil 165 r := endpointConf.HCLBody().SrcRange 166 m := fmt.Sprintf("configuration error: endpoint: %q requires at least one proxy, request or response block", endpointConf.Pattern) 167 return nil, hcl.Diagnostics{&hcl.Diagnostic{ 168 Severity: hcl.DiagError, 169 Summary: m, 170 Subject: &r, 171 }} 172 } 173 174 bodyLimit, err := parseBodyLimit(endpointConf.RequestBodyLimit) 175 if err != nil { 176 r := endpointConf.HCLBody().SrcRange 177 return nil, hcl.Diagnostics{&hcl.Diagnostic{ 178 Severity: hcl.DiagError, 179 Summary: fmt.Sprintf("endpoint: %q: parsing request body limit" + endpointConf.Pattern), 180 Subject: &r, 181 }} 182 } 183 184 bufferOpts := buffer.Must(append(blockBodies, endpointConf.Remain)...) 185 186 apiName := "" 187 if apiConf != nil { 188 apiName = apiConf.Name 189 } 190 191 return &handler.EndpointOptions{ 192 APIName: apiName, 193 Context: endpointConf.HCLBody(), 194 ErrorTemplate: errTpl, 195 Items: endpointConf.Sequences, 196 LogPattern: endpointConf.Pattern, 197 Producers: allProducers, 198 ReqBodyLimit: bodyLimit, 199 BufferOpts: bufferOpts, 200 SendServerTimings: conf.Settings.SendServerTimings, 201 Response: response, 202 ServerOpts: serverOptions, 203 }, nil 204 } 205 206 func markDependencies(allProducers map[string]producer.Roundtrip, items sequence.List) { 207 for _, item := range items { 208 pr := allProducers[item.Name] 209 var prevs []string 210 deps := item.Deps() 211 if deps == nil { 212 continue 213 } 214 for _, dep := range deps { 215 prevs = append(prevs, dep.Name) 216 } 217 pr.SetDependsOn(strings.Join(prevs, ",")) 218 markDependencies(allProducers, deps) 219 } 220 } 221 222 func addIndependentProducers(allProducers map[string]producer.Roundtrip, endpointConf *config.Endpoint) { 223 // TODO simplify 224 allDeps := sequence.Dependencies(endpointConf.Sequences) 225 sortedProducers := server.SortDefault(allProducers) 226 outer: 227 for _, name := range sortedProducers { 228 for _, deps := range allDeps { 229 for _, dep := range deps { 230 if name == dep { 231 continue outer // in sequence 232 } 233 } 234 } 235 endpointConf.Sequences = append(endpointConf.Sequences, &sequence.Item{Name: name}) 236 } 237 }