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  }