github.com/bazelbuild/rules_webtesting@v0.2.0/go/wtl/proxy/driverhub/debugger/debugger.go (about) 1 // Copyright 2017 Google Inc. 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 debugger enables WTL Debugger. 16 package debugger 17 18 import ( 19 "bytes" 20 "context" 21 "encoding/json" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "log" 26 "net" 27 "net/http" 28 "os" 29 "regexp" 30 "sync" 31 32 "github.com/bazelbuild/rules_webtesting/go/errors" 33 ) 34 35 type breakpoint struct { 36 ID int `json:"id"` 37 Path string `json:"path,omitempty"` 38 Methods []string `json:"methods,omitempty"` 39 Body string `josn:"body,omitempty"` 40 41 pathRegex *regexp.Regexp 42 bodyRegex *regexp.Regexp 43 } 44 45 type command struct { 46 ID int `json:"id"` 47 Command string `json:"command"` 48 Breakpoint *breakpoint `json:"breakpoint,omitempty"` 49 } 50 51 type request struct { 52 Method string `json:"method,omitempty"` 53 Path string `json:"path,omitempty"` 54 Body string `json:"body,omitempty"` 55 } 56 57 type response struct { 58 ID int `json:"id"` 59 Status string `json:"status"` 60 Request *request `json:"request,omitempty"` 61 } 62 63 // Debugger is an implementation of the WTL Debugger server. 64 type Debugger struct { 65 conn net.Conn 66 67 mu sync.RWMutex 68 connError error 69 healthy bool 70 step bool 71 breakpoints map[int]*breakpoint 72 waiting chan<- interface{} 73 } 74 75 // New returns a Debugger waiting for a connection on TCP port. 76 func New(port int) *Debugger { 77 d := &Debugger{ 78 breakpoints: map[int]*breakpoint{}, 79 } 80 81 go d.waitForConnection(port) 82 return d 83 } 84 85 // Name is the name of this component used in errors and logging. 86 func (*Debugger) Name() string { 87 return "WTL Debugger Server" 88 } 89 90 // Healthy returns nil iff a frontend is connected and has sent a step or continue command. 91 func (d *Debugger) Healthy(context.Context) error { 92 d.mu.RLock() 93 defer d.mu.RUnlock() 94 95 if d.connError != nil { 96 return d.connError 97 } 98 99 if !d.healthy { 100 return errors.New(d.Name(), "debugger frontend is not connected.") 101 } 102 return nil 103 } 104 105 // Request logs r to the debugger frontend. If r matches a breakpoint or the debugger is in step mode, 106 // Request will not return until a continue message from the front-end is received. 107 func (d *Debugger) Request(r *http.Request) { 108 // Capture request body 109 body, err := capture(r.Body) 110 if err != nil { 111 log.Fatalf("Error reading request body: %v", err) 112 } 113 r.Body = body 114 115 resp := &response{ 116 Request: &request{ 117 Method: r.Method, 118 Path: r.URL.Path, 119 Body: body.captured, 120 }, 121 } 122 123 // Identify if we should be continuing or waiting 124 d.mu.RLock() 125 step := d.step 126 127 if !step { 128 for _, bp := range d.breakpoints { 129 if bp.matches(resp.Request) { 130 step = true 131 break 132 } 133 } 134 } 135 136 d.mu.RUnlock() 137 138 if step { 139 resp.Status = "waiting" 140 } else { 141 resp.Status = "running" 142 } 143 144 // Send request info to client client 145 bytes, err := json.Marshal(resp) 146 if err != nil { 147 log.Print(err) 148 return 149 } 150 151 if _, err := d.conn.Write(bytes); err != nil { 152 log.Print(err) 153 } 154 155 if _, err := d.conn.Write([]byte("\n")); err != nil { 156 log.Print(err) 157 } 158 159 // Not stepping, so return. 160 if !step { 161 return 162 } 163 164 // Wait for step/continue command from front end. 165 166 // TODO(DrMarcII): Race condition here, but it is racing a human, so not too worried about it. 167 waiting := make(chan interface{}) 168 d.mu.Lock() 169 d.waiting = waiting 170 d.mu.Unlock() 171 172 <-waiting 173 d.mu.Lock() 174 d.waiting = nil 175 d.mu.Unlock() 176 } 177 178 func (d *Debugger) waitForConnection(port int) { 179 l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) 180 if err != nil { 181 d.mu.Lock() 182 defer d.mu.Unlock() 183 d.connError = errors.NewPermanent(d.Name(), err) 184 return 185 } 186 187 fmt.Printf(` 188 *********************************************** 189 190 Waiting for debugger connection on port %d. 191 192 *********************************************** 193 `, port) 194 195 conn, err := l.Accept() 196 d.mu.Lock() 197 defer d.mu.Unlock() 198 if err != nil { 199 d.connError = errors.NewPermanent(d.Name(), err) 200 return 201 } 202 d.conn = conn 203 204 go d.readLoop() 205 } 206 207 func (d *Debugger) readLoop() { 208 decoder := json.NewDecoder(d.conn) 209 210 for { 211 cmd := &command{} 212 if err := decoder.Decode(cmd); err != nil { 213 // do something here... 214 log.Fatalf("Error reading from debugger: %v", err) 215 } 216 217 d.processCommand(cmd) 218 } 219 } 220 221 func (d *Debugger) processCommand(cmd *command) { 222 d.mu.Lock() 223 defer d.mu.Unlock() 224 225 response := &response{ID: cmd.ID, Status: "error"} 226 227 switch cmd.Command { 228 case "continue": 229 d.healthy = true 230 d.step = false 231 if d.waiting != nil { 232 close(d.waiting) 233 } 234 response.Status = "running" 235 236 case "step": 237 d.healthy = true 238 d.step = true 239 if d.waiting != nil { 240 close(d.waiting) 241 } 242 response.Status = "running" 243 244 case "stop": 245 os.Exit(-1) 246 case "set breakpoint": 247 if cmd.Breakpoint == nil { 248 break 249 } 250 bp := cmd.Breakpoint 251 if err := bp.initialize(); err != nil { 252 break 253 } 254 d.breakpoints[bp.ID] = bp 255 response.Status = "waiting" 256 257 case "delete breakpoint": 258 if cmd.Breakpoint == nil { 259 break 260 } 261 delete(d.breakpoints, cmd.Breakpoint.ID) 262 response.Status = "waiting" 263 } 264 265 bytes, err := json.Marshal(response) 266 if err != nil { 267 log.Print(err) 268 return 269 } 270 271 if _, err := d.conn.Write(bytes); err != nil { 272 log.Print(err) 273 } 274 } 275 276 func (bp *breakpoint) initialize() error { 277 if bp.Path != "" { 278 r, err := regexp.Compile(bp.Path) 279 if err != nil { 280 return err 281 } 282 bp.pathRegex = r 283 } 284 285 if bp.Body != "" { 286 r, err := regexp.Compile(bp.Body) 287 if err != nil { 288 return err 289 } 290 bp.bodyRegex = r 291 } 292 return nil 293 } 294 295 func (bp *breakpoint) matches(r *request) bool { 296 if bp.pathRegex != nil && bp.pathRegex.FindString(r.Path) == "" { 297 return false 298 } 299 300 if bp.bodyRegex != nil && bp.bodyRegex.FindString(r.Body) == "" { 301 return false 302 } 303 304 if len(bp.Methods) != 0 { 305 found := false 306 for _, method := range bp.Methods { 307 if r.Method == method { 308 found = true 309 break 310 } 311 } 312 if !found { 313 return false 314 } 315 } 316 317 return true 318 } 319 320 type capturedReader struct { 321 io.Reader 322 io.Closer 323 captured string 324 } 325 326 func capture(r io.ReadCloser) (*capturedReader, error) { 327 c, err := ioutil.ReadAll(r) 328 if err != nil { 329 return nil, err 330 } 331 return &capturedReader{ 332 bytes.NewReader(c), 333 r, 334 string(c), 335 }, nil 336 }