2015-03-20 21:11:11 +00:00
|
|
|
package rest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/derekparker/delve/service"
|
|
|
|
"github.com/derekparker/delve/service/api"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Client is a REST service.Client.
|
|
|
|
type RESTClient struct {
|
|
|
|
addr string
|
|
|
|
httpClient *http.Client
|
|
|
|
}
|
|
|
|
|
2015-05-02 11:19:49 +00:00
|
|
|
// ClientError is an error from the debugger.
|
|
|
|
type ClientError struct {
|
|
|
|
// Message is the specific error from the debugger.
|
|
|
|
Message string
|
|
|
|
// Status is the HTTP error from the debugger request.
|
|
|
|
Status string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ClientError) Error() string { return e.Message }
|
|
|
|
|
2015-03-20 21:11:11 +00:00
|
|
|
// Ensure the implementation satisfies the interface.
|
|
|
|
var _ service.Client = &RESTClient{}
|
|
|
|
|
|
|
|
// NewClient creates a new RESTClient.
|
|
|
|
func NewClient(addr string) *RESTClient {
|
|
|
|
return &RESTClient{
|
|
|
|
addr: addr,
|
|
|
|
httpClient: &http.Client{},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) Detach(killProcess bool) error {
|
|
|
|
params := [][]string{{"kill", strconv.FormatBool(killProcess)}}
|
|
|
|
err := c.doGET("/detach", nil, params...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) GetState() (*api.DebuggerState, error) {
|
|
|
|
var state *api.DebuggerState
|
|
|
|
err := c.doGET("/state", &state)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) Continue() (*api.DebuggerState, error) {
|
|
|
|
var state *api.DebuggerState
|
|
|
|
err := c.doPOST("/command", &api.DebuggerCommand{Name: api.Continue}, &state)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) Next() (*api.DebuggerState, error) {
|
|
|
|
var state *api.DebuggerState
|
|
|
|
err := c.doPOST("/command", &api.DebuggerCommand{Name: api.Next}, &state)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) Step() (*api.DebuggerState, error) {
|
|
|
|
var state *api.DebuggerState
|
|
|
|
err := c.doPOST("/command", &api.DebuggerCommand{Name: api.Step}, &state)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) SwitchThread(threadID int) (*api.DebuggerState, error) {
|
|
|
|
var state *api.DebuggerState
|
|
|
|
err := c.doPOST("/command", &api.DebuggerCommand{
|
|
|
|
Name: api.SwitchThread,
|
|
|
|
ThreadID: threadID,
|
|
|
|
}, &state)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) Halt() (*api.DebuggerState, error) {
|
|
|
|
var state *api.DebuggerState
|
|
|
|
err := c.doPOST("/command", &api.DebuggerCommand{Name: api.Halt}, &state)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) GetBreakPoint(id int) (*api.BreakPoint, error) {
|
|
|
|
var breakPoint *api.BreakPoint
|
|
|
|
err := c.doGET(fmt.Sprintf("/breakpoints/%d", id), &breakPoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return breakPoint, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) CreateBreakPoint(breakPoint *api.BreakPoint) (*api.BreakPoint, error) {
|
|
|
|
var newBreakPoint *api.BreakPoint
|
|
|
|
err := c.doPOST("/breakpoints", breakPoint, &newBreakPoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return newBreakPoint, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) ListBreakPoints() ([]*api.BreakPoint, error) {
|
|
|
|
var breakPoints []*api.BreakPoint
|
|
|
|
err := c.doGET("/breakpoints", &breakPoints)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return breakPoints, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) ClearBreakPoint(id int) (*api.BreakPoint, error) {
|
|
|
|
var breakPoint *api.BreakPoint
|
|
|
|
err := c.doDELETE(fmt.Sprintf("/breakpoints/%d", id), &breakPoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return breakPoint, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) ListThreads() ([]*api.Thread, error) {
|
|
|
|
var threads []*api.Thread
|
|
|
|
err := c.doGET("/threads", &threads)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return threads, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) GetThread(id int) (*api.Thread, error) {
|
|
|
|
var thread *api.Thread
|
|
|
|
err := c.doGET(fmt.Sprintf("/threads/%d", id), &thread)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return thread, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) EvalSymbol(symbol string) (*api.Variable, error) {
|
|
|
|
var v *api.Variable
|
|
|
|
err := c.doGET(fmt.Sprintf("/eval/%s", symbol), &v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) EvalSymbolFor(threadID int, symbol string) (*api.Variable, error) {
|
|
|
|
var v *api.Variable
|
|
|
|
err := c.doGET(fmt.Sprintf("/threads/%d/eval/%s", threadID, symbol), &v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) ListSources(filter string) ([]string, error) {
|
|
|
|
params := [][]string{}
|
|
|
|
if len(filter) > 0 {
|
|
|
|
params = append(params, []string{"filter", filter})
|
|
|
|
}
|
|
|
|
var sources []string
|
|
|
|
err := c.doGET("/sources", &sources, params...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return sources, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) ListFunctions(filter string) ([]string, error) {
|
|
|
|
params := [][]string{}
|
|
|
|
if len(filter) > 0 {
|
|
|
|
params = append(params, []string{"filter", filter})
|
|
|
|
}
|
|
|
|
var funcs []string
|
|
|
|
err := c.doGET("/functions", &funcs, params...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return funcs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) ListPackageVariables(filter string) ([]api.Variable, error) {
|
|
|
|
params := [][]string{}
|
|
|
|
if len(filter) > 0 {
|
|
|
|
params = append(params, []string{"filter", filter})
|
|
|
|
}
|
|
|
|
var vars []api.Variable
|
|
|
|
err := c.doGET(fmt.Sprintf("/vars"), &vars, params...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return vars, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) ListPackageVariablesFor(threadID int, filter string) ([]api.Variable, error) {
|
|
|
|
params := [][]string{}
|
|
|
|
if len(filter) > 0 {
|
|
|
|
params = append(params, []string{"filter", filter})
|
|
|
|
}
|
|
|
|
var vars []api.Variable
|
|
|
|
err := c.doGET(fmt.Sprintf("/threads/%d/vars", threadID), &vars, params...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return vars, nil
|
|
|
|
}
|
|
|
|
|
2015-05-08 20:16:22 +00:00
|
|
|
func (c *RESTClient) ListLocalVariables() ([]api.Variable, error) {
|
|
|
|
var vars []api.Variable
|
|
|
|
err := c.doGET("/localvars", &vars)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return vars, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RESTClient) ListFunctionArgs() ([]api.Variable, error) {
|
|
|
|
var vars []api.Variable
|
|
|
|
err := c.doGET("/args", &vars)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return vars, nil
|
|
|
|
}
|
|
|
|
|
2015-03-20 21:11:11 +00:00
|
|
|
func (c *RESTClient) ListGoroutines() ([]*api.Goroutine, error) {
|
|
|
|
var goroutines []*api.Goroutine
|
|
|
|
err := c.doGET("/goroutines", &goroutines)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return goroutines, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: how do we use http.Client with a UNIX socket URI?
|
|
|
|
func (c *RESTClient) url(path string) string {
|
|
|
|
return fmt.Sprintf("http://%s%s", c.addr, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// doGET performs an HTTP GET to path and stores the resulting API object in
|
|
|
|
// obj. Query parameters are passed as an array of 2-element string arrays
|
|
|
|
// representing key-value pairs.
|
|
|
|
func (c *RESTClient) doGET(path string, obj interface{}, params ...[]string) error {
|
|
|
|
url, err := url.Parse(c.url(path))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add any supplied query parameters to the URL
|
|
|
|
q := url.Query()
|
|
|
|
for _, p := range params {
|
|
|
|
q.Set(p[0], p[1])
|
|
|
|
}
|
|
|
|
url.RawQuery = q.Encode()
|
|
|
|
|
|
|
|
// Create the request
|
|
|
|
req, err := http.NewRequest("GET", url.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
|
|
|
|
// Execute the request
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
// Extract error text and return
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
contents, _ := ioutil.ReadAll(resp.Body)
|
2015-05-02 11:19:49 +00:00
|
|
|
return &ClientError{Message: string(contents), Status: resp.Status}
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Decode result object
|
|
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
err = decoder.Decode(&obj)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// doPOST performs an HTTP POST to path, sending 'out' as the body and storing
|
|
|
|
// the resulting API object to 'in'.
|
|
|
|
func (c *RESTClient) doPOST(path string, out interface{}, in interface{}) error {
|
|
|
|
jsonString, err := json.Marshal(out)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", c.url(path), bytes.NewBuffer(jsonString))
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusCreated {
|
|
|
|
contents, _ := ioutil.ReadAll(resp.Body)
|
2015-05-02 11:19:49 +00:00
|
|
|
return &ClientError{Message: string(contents), Status: resp.Status}
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
err = decoder.Decode(&in)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// doDELETE performs an HTTP DELETE to path, storing the resulting API object
|
|
|
|
// to 'obj'.
|
|
|
|
func (c *RESTClient) doDELETE(path string, obj interface{}) error {
|
|
|
|
req, err := http.NewRequest("DELETE", c.url(path), nil)
|
|
|
|
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
contents, _ := ioutil.ReadAll(resp.Body)
|
2015-05-02 11:19:49 +00:00
|
|
|
return &ClientError{Message: string(contents), Status: resp.Status}
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
err = decoder.Decode(&obj)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// doPUT performs an HTTP PUT to path, sending 'out' as the body and storing
|
|
|
|
// the resulting API object to 'in'.
|
|
|
|
func (c *RESTClient) doPUT(path string, out interface{}, in interface{}) error {
|
|
|
|
jsonString, err := json.Marshal(out)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("PUT", c.url(path), bytes.NewBuffer(jsonString))
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
contents, _ := ioutil.ReadAll(resp.Body)
|
2015-05-02 11:19:49 +00:00
|
|
|
return &ClientError{Message: string(contents), Status: resp.Status}
|
2015-03-20 21:11:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
decoder := json.NewDecoder(resp.Body)
|
|
|
|
err = decoder.Decode(&in)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|