github.com/avenga/couper@v1.12.2/definitions/job.go (about)

     1  package definitions
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"time"
     7  
     8  	"github.com/hashicorp/hcl/v2"
     9  	"github.com/sirupsen/logrus"
    10  
    11  	"github.com/avenga/couper/config"
    12  	"github.com/avenga/couper/config/request"
    13  	"github.com/avenga/couper/errors"
    14  	"github.com/avenga/couper/eval"
    15  	"github.com/avenga/couper/eval/variables"
    16  	"github.com/avenga/couper/handler/middleware"
    17  	"github.com/avenga/couper/logging"
    18  	"github.com/avenga/couper/server/writer"
    19  	"github.com/avenga/couper/utils"
    20  )
    21  
    22  type Job struct {
    23  	conf     *config.Job
    24  	handler  http.Handler
    25  	interval time.Duration
    26  	settings *config.Settings
    27  }
    28  
    29  type Jobs []*Job
    30  
    31  func (j Jobs) Run(ctx context.Context, log *logrus.Entry) {
    32  	if len(j) == 0 {
    33  		return
    34  	}
    35  
    36  	logEntry := log.WithContext(ctx)
    37  	logEntry.Data["type"] = "couper_job"
    38  
    39  	for _, job := range j {
    40  		go job.Run(ctx, logEntry)
    41  	}
    42  }
    43  
    44  func NewJob(j *config.Job, h http.Handler, settings *config.Settings) *Job {
    45  	return &Job{
    46  		conf:     j,
    47  		handler:  h,
    48  		interval: j.IntervalDuration,
    49  		settings: settings,
    50  	}
    51  }
    52  
    53  func (j *Job) Run(ctx context.Context, logEntry *logrus.Entry) {
    54  	req, _ := http.NewRequest(http.MethodGet, "", nil)
    55  	req.Header.Set("User-Agent", "Couper / "+utils.VersionName+" job-"+j.conf.Name)
    56  
    57  	uidFn := middleware.NewUIDFunc(j.settings.RequestIDBackendHeader)
    58  
    59  	t := time.NewTicker(time.Millisecond * 50)
    60  	defer t.Stop()
    61  
    62  	firstRun := true
    63  
    64  	clh := middleware.NewCustomLogsHandler([]hcl.Body{j.conf.Remain}, j.handler, j.conf.Name)
    65  
    66  	for {
    67  		select {
    68  		case <-ctx.Done():
    69  			logEntry.WithFields(logrus.Fields{
    70  				"name": j.conf.Name,
    71  			}).Errorf("stopping: %v", ctx.Err())
    72  			return
    73  		case <-t.C:
    74  			uid := uidFn()
    75  
    76  			outReq := req.Clone(context.WithValue(ctx, request.UID, uid))
    77  
    78  			evalCtx := eval.ContextFromRequest(outReq).WithClientRequest(outReq) // setup syncMap, upstream custom logs
    79  			delete(evalCtx.HCLContext().Variables, variables.ClientRequest)      // this is the noop req from above, not helpful
    80  
    81  			outCtx := context.WithValue(evalCtx, request.LogEntry, logEntry)
    82  			outCtx = context.WithValue(outCtx, request.LogCustomAccess, []hcl.Body{j.conf.Remain}) // local custom logs
    83  			outReq = outReq.WithContext(outCtx)
    84  
    85  			n := time.Now()
    86  			log := logEntry.
    87  				WithFields(logrus.Fields{
    88  					"name": j.conf.Name,
    89  					"timings": logging.Fields{
    90  						"total":    logging.RoundMS(time.Since(n)),
    91  						"interval": logging.RoundMS(j.interval),
    92  					},
    93  					"uid": uid,
    94  				}).WithContext(outCtx).
    95  				WithTime(n)
    96  
    97  			go run(outReq, clh, log)
    98  
    99  			if firstRun {
   100  				t.Reset(j.interval)
   101  				firstRun = false
   102  			}
   103  		}
   104  	}
   105  }
   106  
   107  func run(req *http.Request, h http.Handler, log *logrus.Entry) {
   108  	w := writer.NewResponseWriter(&noopResponseWriter{}, "")
   109  	h.ServeHTTP(w, req)
   110  
   111  	if w.StatusCode() == 0 || w.StatusCode() > 499 {
   112  		if ctxErr, ok := req.Context().Value(request.Error).(*errors.Error); ok {
   113  			if len(ctxErr.Kinds()) > 0 {
   114  				log = log.WithFields(logrus.Fields{"error_type": ctxErr.Kinds()[0]})
   115  			}
   116  			log.Error(ctxErr.Error())
   117  			return
   118  		}
   119  		log.Error()
   120  		return
   121  	}
   122  	log.Info()
   123  }
   124  
   125  var _ http.ResponseWriter = &noopResponseWriter{}
   126  
   127  type noopResponseWriter struct {
   128  	header http.Header
   129  }
   130  
   131  func (n *noopResponseWriter) Header() http.Header {
   132  	if n.header == nil {
   133  		n.header = make(http.Header)
   134  	}
   135  	return n.header
   136  }
   137  
   138  func (n *noopResponseWriter) Write(bytes []byte) (int, error) {
   139  	return len(bytes), nil
   140  }
   141  
   142  func (n *noopResponseWriter) WriteHeader(_ int) {}