/* Copyright 2021 The CloudEvents Authors SPDX-License-Identifier: Apache-2.0 */ package http import ( "fmt" "net" nethttp "net/http" "net/url" "strings" "time" ) // Option is the function signature required to be considered an http.Option. type Option func(*Protocol) error // WithTarget sets the outbound recipient of cloudevents when using an HTTP // request. func WithTarget(targetUrl string) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http target option can not set nil protocol") } targetUrl = strings.TrimSpace(targetUrl) if targetUrl != "" { var err error var target *url.URL target, err = url.Parse(targetUrl) if err != nil { return fmt.Errorf("http target option failed to parse target url: %s", err.Error()) } p.Target = target if p.RequestTemplate == nil { p.RequestTemplate = &nethttp.Request{ Method: nethttp.MethodPost, } } p.RequestTemplate.URL = target return nil } return fmt.Errorf("http target option was empty string") } } // WithHeader sets an additional default outbound header for all cloudevents // when using an HTTP request. func WithHeader(key, value string) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http header option can not set nil protocol") } key = strings.TrimSpace(key) if key != "" { if p.RequestTemplate == nil { p.RequestTemplate = &nethttp.Request{ Method: nethttp.MethodPost, } } if p.RequestTemplate.Header == nil { p.RequestTemplate.Header = nethttp.Header{} } p.RequestTemplate.Header.Add(key, value) return nil } return fmt.Errorf("http header option was empty string") } } // WithShutdownTimeout sets the shutdown timeout when the http server is being shutdown. func WithShutdownTimeout(timeout time.Duration) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http shutdown timeout option can not set nil protocol") } p.ShutdownTimeout = timeout return nil } } func checkListen(p *Protocol, prefix string) error { switch { case p.listener.Load() != nil: return fmt.Errorf("error setting %v: listener already set", prefix) } return nil } // WithPort sets the listening port for StartReceiver. // Only one of WithListener or WithPort is allowed. func WithPort(port int) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http port option can not set nil protocol") } if port < 0 || port > 65535 { return fmt.Errorf("http port option was given an invalid port: %d", port) } if err := checkListen(p, "http port option"); err != nil { return err } p.Port = port return nil } } // WithListener sets the listener for StartReceiver. // Only one of WithListener or WithPort is allowed. func WithListener(l net.Listener) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http listener option can not set nil protocol") } if err := checkListen(p, "http listener"); err != nil { return err } p.listener.Store(l) return nil } } // WithPath sets the path to receive cloudevents on for HTTP transports. func WithPath(path string) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http path option can not set nil protocol") } path = strings.TrimSpace(path) if len(path) == 0 { return fmt.Errorf("http path option was given an invalid path: %q", path) } p.Path = path return nil } } // WithMethod sets the HTTP verb (GET, POST, PUT, etc.) to use // when using an HTTP request. func WithMethod(method string) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http method option can not set nil protocol") } method = strings.TrimSpace(method) if method != "" { if p.RequestTemplate == nil { p.RequestTemplate = &nethttp.Request{} } p.RequestTemplate.Method = method return nil } return fmt.Errorf("http method option was empty string") } } // Middleware is a function that takes an existing http.Handler and wraps it in middleware, // returning the wrapped http.Handler. type Middleware func(next nethttp.Handler) nethttp.Handler // WithMiddleware adds an HTTP middleware to the transport. It may be specified multiple times. // Middleware is applied to everything before it. For example // `NewClient(WithMiddleware(foo), WithMiddleware(bar))` would result in `bar(foo(original))`. func WithMiddleware(middleware Middleware) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http middleware option can not set nil protocol") } p.middleware = append(p.middleware, middleware) return nil } } // WithRoundTripper sets the HTTP RoundTripper. func WithRoundTripper(roundTripper nethttp.RoundTripper) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http round tripper option can not set nil protocol") } p.roundTripper = roundTripper return nil } } // WithRoundTripperDecorator decorates the default HTTP RoundTripper chosen. func WithRoundTripperDecorator(decorator func(roundTripper nethttp.RoundTripper) nethttp.RoundTripper) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http round tripper option can not set nil protocol") } if p.roundTripper == nil { if p.Client == nil { p.roundTripper = nethttp.DefaultTransport } else { p.roundTripper = p.Client.Transport } } p.roundTripper = decorator(p.roundTripper) return nil } } // WithClient sets the protocol client func WithClient(client nethttp.Client) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("client option can not set nil protocol") } p.Client = &client return nil } } // WithGetHandlerFunc sets the http GET handler func func WithGetHandlerFunc(fn nethttp.HandlerFunc) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http GET handler func can not set nil protocol") } p.GetHandlerFn = fn return nil } } // WithOptionsHandlerFunc sets the http OPTIONS handler func func WithOptionsHandlerFunc(fn nethttp.HandlerFunc) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http OPTIONS handler func can not set nil protocol") } p.OptionsHandlerFn = fn return nil } } // WithDefaultOptionsHandlerFunc sets the options handler to be the built in handler and configures the options. // methods: the supported methods reported to OPTIONS caller. // rate: the rate limit reported to OPTIONS caller. // origins: the prefix of the accepted origins, or "*". // callback: preform the callback to ACK the OPTIONS request. func WithDefaultOptionsHandlerFunc(methods []string, rate int, origins []string, callback bool) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http OPTIONS handler func can not set nil protocol") } p.OptionsHandlerFn = p.OptionsHandler p.WebhookConfig = &WebhookConfig{ AllowedMethods: methods, AllowedRate: &rate, AllowedOrigins: origins, AutoACKCallback: callback, } return nil } } // IsRetriable is a custom function that can be used to override the // default retriable status codes. type IsRetriable func(statusCode int) bool // WithIsRetriableFunc sets the function that gets called to determine if an // error should be retried. If not set, the defaultIsRetriableFunc is used. func WithIsRetriableFunc(isRetriable IsRetriable) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("isRetriable handler func can not set nil protocol") } if isRetriable == nil { return fmt.Errorf("isRetriable handler can not be nil") } p.isRetriableFunc = isRetriable return nil } } func WithRateLimiter(rl RateLimiter) Option { return func(p *Protocol) error { if p == nil { return fmt.Errorf("http OPTIONS handler func can not set nil protocol") } p.limiter = rl return nil } } // WithRequestDataAtContextMiddleware adds to the Context RequestData. // This enables a user's dispatch handler to inspect HTTP request information by // retrieving it from the Context. func WithRequestDataAtContextMiddleware() Option { return WithMiddleware(func(next nethttp.Handler) nethttp.Handler { return nethttp.HandlerFunc(func(w nethttp.ResponseWriter, r *nethttp.Request) { ctx := WithRequestDataAtContext(r.Context(), r) next.ServeHTTP(w, r.WithContext(ctx)) }) }) }