go.uber.org/yarpc@v1.72.1/transport/http/config.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package http 22 23 import ( 24 "errors" 25 "fmt" 26 "time" 27 28 "go.uber.org/yarpc/api/transport" 29 yarpctls "go.uber.org/yarpc/api/transport/tls" 30 "go.uber.org/yarpc/peer/hostport" 31 "go.uber.org/yarpc/yarpcconfig" 32 ) 33 34 // TransportSpec returns a TransportSpec for the HTTP transport. 35 // 36 // See TransportConfig, InboundConfig, and OutboundConfig for details on the 37 // different configuration parameters supported by this Transport. 38 // 39 // Any Transport, Inbound or Outbound option may be passed to this function. 40 // These options will be applied BEFORE configuration parameters are 41 // interpreted. This allows configuration parameters to override Option 42 // provided to TransportSpec. 43 func TransportSpec(opts ...Option) yarpcconfig.TransportSpec { 44 var ts transportSpec 45 for _, o := range opts { 46 switch opt := o.(type) { 47 case TransportOption: 48 ts.TransportOptions = append(ts.TransportOptions, opt) 49 case InboundOption: 50 ts.InboundOptions = append(ts.InboundOptions, opt) 51 case OutboundOption: 52 ts.OutboundOptions = append(ts.OutboundOptions, opt) 53 default: 54 panic(fmt.Sprintf("unknown option of type %T: %v", o, o)) 55 } 56 } 57 return ts.Spec() 58 } 59 60 // transportSpec holds the configurable parts of the HTTP TransportSpec. 61 // 62 // These are usually runtime dependencies that cannot be parsed from 63 // configuration. 64 type transportSpec struct { 65 TransportOptions []TransportOption 66 InboundOptions []InboundOption 67 OutboundOptions []OutboundOption 68 } 69 70 func (ts *transportSpec) Spec() yarpcconfig.TransportSpec { 71 return yarpcconfig.TransportSpec{ 72 Name: TransportName, 73 BuildTransport: ts.buildTransport, 74 BuildInbound: ts.buildInbound, 75 BuildUnaryOutbound: ts.buildUnaryOutbound, 76 BuildOnewayOutbound: ts.buildOnewayOutbound, 77 } 78 } 79 80 // TransportConfig configures the shared HTTP Transport. This is shared 81 // between all HTTP outbounds and inbounds of a Dispatcher. 82 // 83 // transports: 84 // http: 85 // keepAlive: 30s 86 // maxIdleConns: 2 87 // maxIdleConnsPerHost: 2 88 // idleConnTimeout: 90s 89 // disableKeepAlives: false 90 // disableCompression: false 91 // responseHeaderTimeout: 0s 92 // connTimeout: 500ms 93 // connBackoff: 94 // exponential: 95 // first: 10ms 96 // max: 30s 97 // 98 // All parameters of TransportConfig are optional. This section may be omitted 99 // in the transports section. 100 type TransportConfig struct { 101 // Specifies the keep-alive period for all HTTP clients. This field is 102 // optional. 103 KeepAlive time.Duration `config:"keepAlive"` 104 MaxIdleConns int `config:"maxIdleConns"` 105 MaxIdleConnsPerHost int `config:"maxIdleConnsPerHost"` 106 IdleConnTimeout time.Duration `config:"idleConnTimeout"` 107 DisableKeepAlives bool `config:"disableKeepAlives"` 108 DisableCompression bool `config:"disableCompression"` 109 ResponseHeaderTimeout time.Duration `config:"responseHeaderTimeout"` 110 ConnTimeout time.Duration `config:"connTimeout"` 111 ConnBackoff yarpcconfig.Backoff `config:"connBackoff"` 112 } 113 114 func (ts *transportSpec) buildTransport(tc *TransportConfig, k *yarpcconfig.Kit) (transport.Transport, error) { 115 options := newTransportOptions() 116 117 for _, opt := range ts.TransportOptions { 118 opt(&options) 119 } 120 121 if options.serviceName == "" { 122 options.serviceName = k.ServiceName() 123 } 124 if tc.KeepAlive > 0 { 125 options.keepAlive = tc.KeepAlive 126 } 127 if tc.MaxIdleConns > 0 { 128 options.maxIdleConns = tc.MaxIdleConns 129 } 130 if tc.MaxIdleConnsPerHost > 0 { 131 options.maxIdleConnsPerHost = tc.MaxIdleConnsPerHost 132 } 133 if tc.IdleConnTimeout > 0 { 134 options.idleConnTimeout = tc.IdleConnTimeout 135 } 136 if tc.DisableKeepAlives { 137 options.disableKeepAlives = true 138 } 139 if tc.DisableCompression { 140 options.disableCompression = true 141 } 142 if tc.ResponseHeaderTimeout > 0 { 143 options.responseHeaderTimeout = tc.ResponseHeaderTimeout 144 } 145 if tc.ConnTimeout > 0 { 146 options.connTimeout = tc.ConnTimeout 147 } 148 149 strategy, err := tc.ConnBackoff.Strategy() 150 if err != nil { 151 return nil, err 152 } 153 options.connBackoffStrategy = strategy 154 155 return options.newTransport(), nil 156 } 157 158 // InboundConfig configures an HTTP inbound. 159 // 160 // inbounds: 161 // http: 162 // address: ":80" 163 // grabHeaders: 164 // - x-foo 165 // - x-bar 166 // shutdownTimeout: 5s 167 type InboundConfig struct { 168 // Address to listen on. This field is required. 169 Address string `config:"address,interpolate"` 170 // The additional headers, starting with x, that should be 171 // propagated to handlers. This field is optional. 172 GrabHeaders []string `config:"grabHeaders"` 173 // The maximum amount of time to wait for the inbound to shutdown. 174 ShutdownTimeout *time.Duration `config:"shutdownTimeout"` 175 // TLS configuration of the inbound. 176 TLSConfig TLSConfig `config:"tls"` 177 } 178 179 // TLSConfig specifies the TLS configuration of the HTTP inbound. 180 type TLSConfig struct { 181 // Mode when set to Permissive or Enforced enables TLS inbound and 182 // TLS configuration must be passed as an inbound option. 183 Mode yarpctls.Mode `config:"mode,interpolate"` 184 } 185 186 func (ts *transportSpec) buildInbound(ic *InboundConfig, t transport.Transport, k *yarpcconfig.Kit) (transport.Inbound, error) { 187 if ic.Address == "" { 188 return nil, fmt.Errorf("inbound address is required") 189 } 190 191 // TLS mode provided in the inbound options takes higher precedence than 192 // the TLS mode passed in YAML config. 193 inboundOptions := append([]InboundOption{InboundTLSMode(ic.TLSConfig.Mode)}, ts.InboundOptions...) 194 if len(ic.GrabHeaders) > 0 { 195 inboundOptions = append(inboundOptions, GrabHeaders(ic.GrabHeaders...)) 196 } 197 198 if ic.ShutdownTimeout != nil { 199 if *ic.ShutdownTimeout < 0 { 200 return nil, fmt.Errorf("shutdownTimeout must not be negative, got: %q", ic.ShutdownTimeout) 201 } 202 inboundOptions = append(inboundOptions, ShutdownTimeout(*ic.ShutdownTimeout)) 203 } 204 205 return t.(*Transport).NewInbound(ic.Address, inboundOptions...), nil 206 } 207 208 // OutboundConfig configures an HTTP outbound. 209 // 210 // outbounds: 211 // keyvalueservice: 212 // http: 213 // url: "http://127.0.0.1:80/" 214 // 215 // The HTTP outbound supports both, Unary and Oneway transport types. To use 216 // it for only one of these, nest the section inside a "unary" or "onewy" 217 // section. 218 // 219 // outbounds: 220 // keyvalueservice: 221 // unary: 222 // http: 223 // url: "http://127.0.0.1:80/" 224 // 225 // An HTTP outbound can also configure a peer list. 226 // In this case, there can still be a "url" and it serves as a template for the 227 // HTTP client, expressing whether to use "http:" or "https:" and what path to 228 // use. The address gets replaced with peers from the peer list. 229 // 230 // outbounds: 231 // keyvalueservice: 232 // unary: 233 // http: 234 // url: "https://address/rpc" 235 // round-robin: 236 // peers: 237 // - 127.0.0.1:8080 238 // - 127.0.0.1:8081 239 type OutboundConfig struct { 240 yarpcconfig.PeerChooser 241 242 // URL to which requests will be sent for this outbound. This field is 243 // required. 244 URL string `config:"url,interpolate"` 245 // HTTP headers that will be added to all requests made through this 246 // outbound. 247 // 248 // http: 249 // url: "http://localhost:8080/yarpc" 250 // addHeaders: 251 // X-Caller: myserice 252 // X-Token: foo 253 AddHeaders map[string]string `config:"addHeaders"` 254 // TLS config enables TLS outbound. 255 // 256 // http: 257 // url: "http://localhost:8080/yarpc" 258 // tls: 259 // mode: enforced 260 // spiffe-ids: 261 // - destination-id 262 TLS OutboundTLSConfig `config:"tls"` 263 } 264 265 // OutboundTLSConfig configures TLS for the HTTP outbound. 266 type OutboundTLSConfig struct { 267 // Mode when set to Enforced enables outbound TLS. 268 // Note: outbound TLS configuration provider must be given as an option 269 // which is used for fetching client tls.Config. 270 Mode yarpctls.Mode `config:"mode,interpolate"` 271 // SpiffeIDs is list of accepted server spiffe IDs. This cannot be empty 272 // list. 273 SpiffeIDs []string `config:"spiffe-ids"` 274 } 275 276 func (o OutboundTLSConfig) options(provider yarpctls.OutboundTLSConfigProvider) ([]OutboundOption, error) { 277 if o.Mode == yarpctls.Disabled { 278 return nil, nil 279 } 280 281 if o.Mode == yarpctls.Permissive { 282 return nil, errors.New("outbound does not support permissive TLS mode") 283 } 284 285 if provider == nil { 286 return nil, errors.New("outbound TLS enforced but outbound TLS config provider is nil") 287 } 288 289 config, err := provider.ClientTLSConfig(o.SpiffeIDs) 290 if err != nil { 291 return nil, err 292 } 293 294 return []OutboundOption{OutboundTLSConfiguration(config)}, nil 295 } 296 297 func (ts *transportSpec) buildOutbound(oc *OutboundConfig, t transport.Transport, k *yarpcconfig.Kit) (*Outbound, error) { 298 x := t.(*Transport) 299 300 opts := []OutboundOption{OutboundDestinationServiceName(k.OutboundServiceName())} 301 opts = append(opts, ts.OutboundOptions...) 302 if len(oc.AddHeaders) > 0 { 303 for k, v := range oc.AddHeaders { 304 opts = append(opts, AddHeader(k, v)) 305 } 306 } 307 308 option, err := oc.TLS.options(x.ouboundTLSConfigProvider) 309 if err != nil { 310 return nil, err 311 } 312 opts = append(option, opts...) 313 314 // Special case where the URL implies the single peer. 315 if oc.Empty() { 316 return x.NewSingleOutbound(oc.URL, opts...), nil 317 } 318 319 chooser, err := oc.BuildPeerChooser(x, hostport.Identify, k) 320 if err != nil { 321 return nil, fmt.Errorf("cannot configure peer chooser for HTTP outbound: %v", err) 322 } 323 324 if oc.URL != "" { 325 opts = append(opts, URLTemplate(oc.URL)) 326 } 327 return x.NewOutbound(chooser, opts...), nil 328 } 329 330 func (ts *transportSpec) buildUnaryOutbound(oc *OutboundConfig, t transport.Transport, k *yarpcconfig.Kit) (transport.UnaryOutbound, error) { 331 return ts.buildOutbound(oc, t, k) 332 } 333 334 func (ts *transportSpec) buildOnewayOutbound(oc *OutboundConfig, t transport.Transport, k *yarpcconfig.Kit) (transport.OnewayOutbound, error) { 335 return ts.buildOutbound(oc, t, k) 336 }