1// Copyright 2015 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package fifo provides Group, which is a list of modifiers that are executed 16// consecutively. By default, when an error is returned by a modifier, the 17// execution of the modifiers is halted, and the error is returned. Optionally, 18// when errror aggregation is enabled (by calling SetAggretateErrors(true)), modifier 19// execution is not halted, and errors are aggretated and returned after all 20// modifiers have been executed. 21package fifo 22 23import ( 24 "encoding/json" 25 "net/http" 26 "sync" 27 28 "github.com/google/martian/v3" 29 "github.com/google/martian/v3/log" 30 "github.com/google/martian/v3/parse" 31 "github.com/google/martian/v3/verify" 32) 33 34// Group is a martian.RequestResponseModifier that maintains lists of 35// request and response modifiers executed on a first-in, first-out basis. 36type Group struct { 37 reqmu sync.RWMutex 38 reqmods []martian.RequestModifier 39 40 resmu sync.RWMutex 41 resmods []martian.ResponseModifier 42 43 aggregateErrors bool 44} 45 46type groupJSON struct { 47 Modifiers []json.RawMessage `json:"modifiers"` 48 Scope []parse.ModifierType `json:"scope"` 49 AggregateErrors bool `json:"aggregateErrors"` 50} 51 52func init() { 53 parse.Register("fifo.Group", groupFromJSON) 54} 55 56// NewGroup returns a modifier group. 57func NewGroup() *Group { 58 return &Group{} 59} 60 61// SetAggregateErrors sets the error behavior for the Group. When true, the Group will 62// continue to execute consecutive modifiers when a modifier in the group encounters an 63// error. The Group will then return all errors returned by each modifier after all 64// modifiers have been executed. When false, if an error is returned by a modifier, the 65// error is returned by ModifyRequest/Response and no further modifiers are run. 66// By default, error aggregation is disabled. 67func (g *Group) SetAggregateErrors(aggerr bool) { 68 g.aggregateErrors = aggerr 69} 70 71// AddRequestModifier adds a RequestModifier to the group's list of request modifiers. 72func (g *Group) AddRequestModifier(reqmod martian.RequestModifier) { 73 g.reqmu.Lock() 74 defer g.reqmu.Unlock() 75 76 g.reqmods = append(g.reqmods, reqmod) 77} 78 79// AddResponseModifier adds a ResponseModifier to the group's list of response modifiers. 80func (g *Group) AddResponseModifier(resmod martian.ResponseModifier) { 81 g.resmu.Lock() 82 defer g.resmu.Unlock() 83 84 g.resmods = append(g.resmods, resmod) 85} 86 87// ModifyRequest modifies the request. By default, aggregateErrors is false; if an error is 88// returned by a RequestModifier the error is returned and no further modifiers are run. When 89// aggregateErrors is set to true, the errors returned by each modifier in the group are 90// aggregated. 91func (g *Group) ModifyRequest(req *http.Request) error { 92 log.Debugf("fifo.ModifyRequest: %s", req.URL) 93 g.reqmu.RLock() 94 defer g.reqmu.RUnlock() 95 96 merr := martian.NewMultiError() 97 98 for _, reqmod := range g.reqmods { 99 if err := reqmod.ModifyRequest(req); err != nil { 100 if g.aggregateErrors { 101 merr.Add(err) 102 continue 103 } 104 105 return err 106 } 107 } 108 109 if merr.Empty() { 110 return nil 111 } 112 113 return merr 114} 115 116// ModifyResponse modifies the request. By default, aggregateErrors is false; if an error is 117// returned by a RequestModifier the error is returned and no further modifiers are run. When 118// aggregateErrors is set to true, the errors returned by each modifier in the group are 119// aggregated. 120func (g *Group) ModifyResponse(res *http.Response) error { 121 requ := "" 122 if res.Request != nil { 123 requ = res.Request.URL.String() 124 log.Debugf("fifo.ModifyResponse: %s", requ) 125 } 126 g.resmu.RLock() 127 defer g.resmu.RUnlock() 128 129 merr := martian.NewMultiError() 130 131 for _, resmod := range g.resmods { 132 if err := resmod.ModifyResponse(res); err != nil { 133 if g.aggregateErrors { 134 merr.Add(err) 135 continue 136 } 137 138 return err 139 } 140 } 141 142 if merr.Empty() { 143 return nil 144 } 145 146 return merr 147} 148 149// VerifyRequests returns a MultiError containing all the 150// verification errors returned by request verifiers. 151func (g *Group) VerifyRequests() error { 152 log.Debugf("fifo.VerifyRequests()") 153 g.reqmu.Lock() 154 defer g.reqmu.Unlock() 155 156 merr := martian.NewMultiError() 157 for _, reqmod := range g.reqmods { 158 reqv, ok := reqmod.(verify.RequestVerifier) 159 if !ok { 160 continue 161 } 162 163 if err := reqv.VerifyRequests(); err != nil { 164 merr.Add(err) 165 } 166 } 167 168 if merr.Empty() { 169 return nil 170 } 171 172 return merr 173} 174 175// VerifyResponses returns a MultiError containing all the 176// verification errors returned by response verifiers. 177func (g *Group) VerifyResponses() error { 178 log.Debugf("fifo.VerifyResponses()") 179 g.resmu.Lock() 180 defer g.resmu.Unlock() 181 182 merr := martian.NewMultiError() 183 for _, resmod := range g.resmods { 184 resv, ok := resmod.(verify.ResponseVerifier) 185 if !ok { 186 continue 187 } 188 189 if err := resv.VerifyResponses(); err != nil { 190 merr.Add(err) 191 } 192 } 193 194 if merr.Empty() { 195 return nil 196 } 197 198 return merr 199} 200 201// ResetRequestVerifications resets the state of the contained request verifiers. 202func (g *Group) ResetRequestVerifications() { 203 log.Debugf("fifo.ResetRequestVerifications()") 204 g.reqmu.Lock() 205 defer g.reqmu.Unlock() 206 207 for _, reqmod := range g.reqmods { 208 if reqv, ok := reqmod.(verify.RequestVerifier); ok { 209 reqv.ResetRequestVerifications() 210 } 211 } 212} 213 214// ResetResponseVerifications resets the state of the contained request verifiers. 215func (g *Group) ResetResponseVerifications() { 216 log.Debugf("fifo.ResetResponseVerifications()") 217 g.resmu.Lock() 218 defer g.resmu.Unlock() 219 220 for _, resmod := range g.resmods { 221 if resv, ok := resmod.(verify.ResponseVerifier); ok { 222 resv.ResetResponseVerifications() 223 } 224 } 225} 226 227// groupFromJSON builds a fifo.Group from JSON. 228// 229// Example JSON: 230// { 231// "fifo.Group" : { 232// "scope": ["request", "result"], 233// "modifiers": [ 234// { ... }, 235// { ... }, 236// ] 237// } 238// } 239func groupFromJSON(b []byte) (*parse.Result, error) { 240 msg := &groupJSON{} 241 if err := json.Unmarshal(b, msg); err != nil { 242 return nil, err 243 } 244 245 g := NewGroup() 246 if msg.AggregateErrors { 247 g.SetAggregateErrors(true) 248 } 249 250 for _, m := range msg.Modifiers { 251 r, err := parse.FromJSON(m) 252 if err != nil { 253 return nil, err 254 } 255 256 reqmod := r.RequestModifier() 257 if reqmod != nil { 258 g.AddRequestModifier(reqmod) 259 } 260 261 resmod := r.ResponseModifier() 262 if resmod != nil { 263 g.AddResponseModifier(resmod) 264 } 265 } 266 267 return parse.NewResult(g, msg.Scope) 268} 269