github.com/avenga/couper@v1.12.2/handler/producer/request.go (about)

     1  package producer
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/hcl/v2/hclsyntax"
     9  	semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
    10  	"go.opentelemetry.io/otel/trace"
    11  
    12  	"github.com/avenga/couper/config/request"
    13  	"github.com/avenga/couper/eval"
    14  	"github.com/avenga/couper/internal/seetie"
    15  	"github.com/avenga/couper/telemetry"
    16  )
    17  
    18  // Request represents the producer <Request> object.
    19  type Request struct {
    20  	Backend   http.RoundTripper
    21  	Context   *hclsyntax.Body
    22  	Name      string // label
    23  	dependsOn string
    24  }
    25  
    26  func (r *Request) Produce(req *http.Request) *Result {
    27  	ctx := req.Context()
    28  	var rootSpan trace.Span
    29  	ctx, rootSpan = telemetry.NewSpanFromContext(ctx, "requests", trace.WithSpanKind(trace.SpanKindProducer))
    30  
    31  	hclCtx := eval.ContextFromRequest(req).HCLContextSync() // also synced for requests due to sequence case
    32  
    33  	// span end by result reader
    34  	outCtx, span := telemetry.NewSpanFromContext(withRoundTripName(ctx, r.Name), r.Name, trace.WithSpanKind(trace.SpanKindClient))
    35  	if r.dependsOn != "" {
    36  		outCtx = context.WithValue(outCtx, request.EndpointSequenceDependsOn, r.dependsOn)
    37  	}
    38  
    39  	methodVal, err := eval.ValueFromBodyAttribute(hclCtx, r.Context, "method")
    40  	if err != nil {
    41  		return &Result{Err: err}
    42  	}
    43  	method := seetie.ValueToString(methodVal)
    44  
    45  	outreq := req.Clone(req.Context())
    46  	removeHost(outreq)
    47  
    48  	url, err := NewURLFromAttribute(hclCtx, r.Context, "url", outreq)
    49  	if err != nil {
    50  		return &Result{Err: err}
    51  	}
    52  
    53  	body, defaultContentType, err := eval.GetBody(hclCtx, r.Context)
    54  	if err != nil {
    55  		return &Result{Err: err}
    56  	}
    57  
    58  	if method == "" {
    59  		method = http.MethodGet
    60  
    61  		if len(body) > 0 {
    62  			method = http.MethodPost
    63  		}
    64  	}
    65  
    66  	outreq, err = http.NewRequest(strings.ToUpper(method), url.String(), nil)
    67  	if err != nil {
    68  		return &Result{Err: err}
    69  	}
    70  
    71  	expStatusVal, err := eval.ValueFromBodyAttribute(hclCtx, r.Context, "expected_status")
    72  	if err != nil {
    73  		return &Result{Err: err}
    74  	}
    75  
    76  	outCtx = context.WithValue(outCtx, request.EndpointExpectedStatus, seetie.ValueToIntSlice(expStatusVal))
    77  
    78  	if defaultContentType != "" {
    79  		outreq.Header.Set("Content-Type", defaultContentType)
    80  	}
    81  
    82  	eval.SetBody(outreq, []byte(body))
    83  
    84  	outreq = outreq.WithContext(outCtx)
    85  	err = eval.ApplyRequestContext(hclCtx, r.Context, outreq)
    86  	if err != nil {
    87  		return &Result{Err: err}
    88  	}
    89  
    90  	span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(outreq)...)
    91  
    92  	result := roundtrip(r.Backend, outreq)
    93  
    94  	rootSpan.End()
    95  	return result
    96  }
    97  
    98  func (r *Request) SetDependsOn(ps string) {
    99  	r.dependsOn = ps
   100  }
   101  
   102  func withRoundTripName(ctx context.Context, name string) context.Context {
   103  	n := name
   104  	if n == "" {
   105  		n = "default"
   106  	}
   107  	return context.WithValue(ctx, request.RoundTripName, n)
   108  }