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  }