Remove HTTP server/client in favor of JSON-RPC
Instead of maintaining two separate client / server implementations, maintain only the more lightweight JSON-RPC service. The reasoning behind the merging of the original HTTP service was ease of tooling, in other words low barrier of entry for external clients (editor integrations, etc...). I believe the JSON-RPC solution still satisfies that constraint while have the advantage of being a more lightweight solution. HTTP, while highly supported in most modern languages, carries with it too many features we would never take advantage of. The RPC architecture seems a more natural approach. The infrastructure set up during the initial HTTP service implementation was leveraged in the JSON-RPC implementation, so if any of those original authors are reading this commit message: thank you for that work, it was not in vain even if though the original HTTP service is not being removed.
This commit is contained in:
parent
6817bfc2ba
commit
7c8fd02685
@ -13,7 +13,6 @@ import (
|
||||
sys "golang.org/x/sys/unix"
|
||||
|
||||
"github.com/derekparker/delve/service"
|
||||
"github.com/derekparker/delve/service/rest"
|
||||
"github.com/derekparker/delve/service/rpc"
|
||||
"github.com/derekparker/delve/terminal"
|
||||
)
|
||||
@ -40,13 +39,11 @@ func main() {
|
||||
var addr string
|
||||
var logEnabled bool
|
||||
var headless bool
|
||||
var http bool
|
||||
|
||||
flag.BoolVar(&printv, "version", false, "Print version number and exit.")
|
||||
flag.StringVar(&addr, "addr", "localhost:0", "Debugging server listen address.")
|
||||
flag.BoolVar(&logEnabled, "log", false, "Enable debugging server logging.")
|
||||
flag.BoolVar(&headless, "headless", false, "Run in headless mode.")
|
||||
flag.BoolVar(&http, "http", false, "Start HTTP server instead of RPC.")
|
||||
flag.Parse()
|
||||
|
||||
if flag.NFlag() == 0 && len(flag.Args()) == 0 {
|
||||
@ -64,12 +61,12 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
status := run(addr, logEnabled, headless, http)
|
||||
status := run(addr, logEnabled, headless)
|
||||
fmt.Println("[Hope I was of service hunting your bug!]")
|
||||
os.Exit(status)
|
||||
}
|
||||
|
||||
func run(addr string, logEnabled, headless, http bool) int {
|
||||
func run(addr string, logEnabled, headless bool) int {
|
||||
// Collect launch arguments
|
||||
var processArgs []string
|
||||
var attachPid int
|
||||
@ -127,30 +124,18 @@ func run(addr string, logEnabled, headless, http bool) int {
|
||||
|
||||
// Create and start a debugger server
|
||||
var server service.Server
|
||||
if http {
|
||||
server = rest.NewServer(&service.Config{
|
||||
Listener: listener,
|
||||
ProcessArgs: processArgs,
|
||||
AttachPid: attachPid,
|
||||
}, logEnabled)
|
||||
} else {
|
||||
server = rpc.NewServer(&service.Config{
|
||||
Listener: listener,
|
||||
ProcessArgs: processArgs,
|
||||
AttachPid: attachPid,
|
||||
}, logEnabled)
|
||||
}
|
||||
go server.Run()
|
||||
|
||||
var status int
|
||||
if !headless {
|
||||
// Create and start a terminal
|
||||
var client service.Client
|
||||
if http {
|
||||
client = rest.NewClient(listener.Addr().String())
|
||||
} else {
|
||||
client = rpc.NewClient(listener.Addr().String())
|
||||
}
|
||||
term := terminal.New(client)
|
||||
err, status = term.Run()
|
||||
} else {
|
||||
|
||||
@ -1,410 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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 }
|
||||
|
||||
// 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) EvalVariable(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) EvalVariableFor(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
|
||||
}
|
||||
|
||||
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) ListRegisters() (string, error) {
|
||||
var regs string
|
||||
err := c.doGET("/regs", ®s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return regs, 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *RESTClient) Stacktrace(goroutineId, depth int) ([]*api.Location, error) {
|
||||
var locations []*api.Location
|
||||
err := c.doGET(fmt.Sprintf("/goroutines/%d/trace?depth=%d", goroutineId, depth), &locations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return locations, 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)
|
||||
return &ClientError{Message: string(contents), Status: resp.Status}
|
||||
}
|
||||
|
||||
// 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)
|
||||
return &ClientError{Message: string(contents), Status: resp.Status}
|
||||
}
|
||||
|
||||
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)
|
||||
return &ClientError{Message: string(contents), Status: resp.Status}
|
||||
}
|
||||
|
||||
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)
|
||||
return &ClientError{Message: string(contents), Status: resp.Status}
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
err = decoder.Decode(&in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
// Package rest provides RESTful HTTP client and server implementations.
|
||||
package rest
|
||||
@ -1,442 +0,0 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
restful "github.com/emicklei/go-restful"
|
||||
|
||||
"github.com/derekparker/delve/service"
|
||||
"github.com/derekparker/delve/service/api"
|
||||
"github.com/derekparker/delve/service/debugger"
|
||||
)
|
||||
|
||||
// RESTServer exposes a Debugger via a HTTP REST API.
|
||||
type RESTServer struct {
|
||||
// config is all the information necessary to start the debugger and server.
|
||||
config *service.Config
|
||||
// listener is used to serve HTTP.
|
||||
listener net.Listener
|
||||
// debugger is a debugger service.
|
||||
debugger *debugger.Debugger
|
||||
}
|
||||
|
||||
// NewServer creates a new RESTServer.
|
||||
func NewServer(config *service.Config, logEnabled bool) *RESTServer {
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||||
if !logEnabled {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
return &RESTServer{
|
||||
config: config,
|
||||
listener: config.Listener,
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts a debugger and exposes it with an HTTP server. The debugger
|
||||
// itself can be stopped with the `detach` API. Run blocks until the HTTP
|
||||
// server stops.
|
||||
func (s *RESTServer) Run() error {
|
||||
var err error
|
||||
// Create and start the debugger
|
||||
if s.debugger, err = debugger.New(&debugger.Config{
|
||||
ProcessArgs: s.config.ProcessArgs,
|
||||
AttachPid: s.config.AttachPid,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set up the HTTP server
|
||||
container := restful.NewContainer()
|
||||
ws := new(restful.WebService)
|
||||
ws.
|
||||
Path("").
|
||||
Consumes(restful.MIME_JSON).
|
||||
Produces(restful.MIME_JSON).
|
||||
Route(ws.GET("/state").To(s.getState)).
|
||||
Route(ws.GET("/breakpoints").To(s.listBreakpoints)).
|
||||
Route(ws.GET("/breakpoints/{breakpoint-id}").To(s.getBreakpoint)).
|
||||
Route(ws.POST("/breakpoints").To(s.createBreakpoint)).
|
||||
Route(ws.DELETE("/breakpoints/{breakpoint-id}").To(s.clearBreakpoint)).
|
||||
Route(ws.GET("/threads").To(s.listThreads)).
|
||||
Route(ws.GET("/threads/{thread-id}").To(s.getThread)).
|
||||
Route(ws.GET("/threads/{thread-id}/vars").To(s.listThreadPackageVars)).
|
||||
Route(ws.GET("/threads/{thread-id}/eval/{symbol}").To(s.evalThreadSymbol)).
|
||||
Route(ws.GET("/goroutines").To(s.listGoroutines)).
|
||||
Route(ws.GET("/goroutines/{goroutine-id}/trace").To(s.stacktraceGoroutine)).
|
||||
Route(ws.POST("/command").To(s.doCommand)).
|
||||
Route(ws.GET("/sources").To(s.listSources)).
|
||||
Route(ws.GET("/functions").To(s.listFunctions)).
|
||||
Route(ws.GET("/regs").To(s.listRegisters)).
|
||||
Route(ws.GET("/vars").To(s.listPackageVars)).
|
||||
Route(ws.GET("/localvars").To(s.listLocalVars)).
|
||||
Route(ws.GET("/args").To(s.listFunctionArgs)).
|
||||
Route(ws.GET("/eval/{symbol}").To(s.evalSymbol)).
|
||||
// TODO: GET might be the wrong verb for this
|
||||
Route(ws.GET("/detach").To(s.detach))
|
||||
container.Add(ws)
|
||||
|
||||
// Start the HTTP server
|
||||
log.Printf("server listening on %s", s.listener.Addr())
|
||||
return http.Serve(s.listener, container)
|
||||
}
|
||||
|
||||
// Stop detaches from the debugger and waits for it to stop.
|
||||
func (s *RESTServer) Stop(kill bool) error {
|
||||
return s.debugger.Detach(kill)
|
||||
}
|
||||
|
||||
// writeError writes a simple error response.
|
||||
func writeError(response *restful.Response, statusCode int, message string) {
|
||||
response.AddHeader("Content-Type", "text/plain")
|
||||
response.WriteErrorString(statusCode, message)
|
||||
}
|
||||
|
||||
// detach stops the debugger and waits for it to shut down before returning an
|
||||
// OK response. Clients expect this to be a synchronous call.
|
||||
func (s *RESTServer) detach(request *restful.Request, response *restful.Response) {
|
||||
kill, err := strconv.ParseBool(request.QueryParameter("kill"))
|
||||
if err != nil {
|
||||
writeError(response, http.StatusBadRequest, "invalid kill parameter")
|
||||
return
|
||||
}
|
||||
|
||||
err = s.Stop(kill)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *RESTServer) getState(request *restful.Request, response *restful.Response) {
|
||||
state, err := s.debugger.State()
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
response.WriteEntity(state)
|
||||
}
|
||||
|
||||
func (s *RESTServer) doCommand(request *restful.Request, response *restful.Response) {
|
||||
command := new(api.DebuggerCommand)
|
||||
err := request.ReadEntity(command)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
state, err := s.debugger.Command(command)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
response.WriteEntity(state)
|
||||
}
|
||||
|
||||
func (s *RESTServer) getBreakpoint(request *restful.Request, response *restful.Response) {
|
||||
id, err := strconv.Atoi(request.PathParameter("breakpoint-id"))
|
||||
if err != nil {
|
||||
writeError(response, http.StatusBadRequest, "invalid breakpoint id")
|
||||
return
|
||||
}
|
||||
|
||||
found := s.debugger.FindBreakpoint(id)
|
||||
if found == nil {
|
||||
writeError(response, http.StatusNotFound, "breakpoint not found")
|
||||
return
|
||||
}
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(found)
|
||||
}
|
||||
|
||||
func (s *RESTServer) stacktraceGoroutine(request *restful.Request, response *restful.Response) {
|
||||
goroutineId, err := strconv.Atoi(request.PathParameter("goroutine-id"))
|
||||
if err != nil {
|
||||
writeError(response, http.StatusBadRequest, "invalid goroutine id")
|
||||
return
|
||||
}
|
||||
|
||||
depth, err := strconv.Atoi(request.QueryParameter("depth"))
|
||||
if err != nil {
|
||||
writeError(response, http.StatusBadRequest, "invalid depth")
|
||||
return
|
||||
}
|
||||
|
||||
locations, err := s.debugger.Stacktrace(goroutineId, depth)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(locations)
|
||||
}
|
||||
|
||||
func (s *RESTServer) listBreakpoints(request *restful.Request, response *restful.Response) {
|
||||
response.WriteEntity(s.debugger.Breakpoints())
|
||||
}
|
||||
|
||||
func (s *RESTServer) createBreakpoint(request *restful.Request, response *restful.Response) {
|
||||
incomingBp := new(api.Breakpoint)
|
||||
err := request.ReadEntity(incomingBp)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(incomingBp.File) == 0 && len(incomingBp.FunctionName) == 0 {
|
||||
writeError(response, http.StatusBadRequest, "no file or function name provided")
|
||||
return
|
||||
}
|
||||
|
||||
createdbp, err := s.debugger.CreateBreakpoint(incomingBp)
|
||||
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusCreated)
|
||||
response.WriteEntity(createdbp)
|
||||
}
|
||||
|
||||
func (s *RESTServer) clearBreakpoint(request *restful.Request, response *restful.Response) {
|
||||
id, err := strconv.Atoi(request.PathParameter("breakpoint-id"))
|
||||
if err != nil {
|
||||
writeError(response, http.StatusBadRequest, "invalid breakpoint id")
|
||||
return
|
||||
}
|
||||
|
||||
found := s.debugger.FindBreakpoint(id)
|
||||
if found == nil {
|
||||
writeError(response, http.StatusNotFound, "breakpoint not found")
|
||||
return
|
||||
}
|
||||
|
||||
deleted, err := s.debugger.ClearBreakpoint(found)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(deleted)
|
||||
}
|
||||
|
||||
func (s *RESTServer) listThreads(request *restful.Request, response *restful.Response) {
|
||||
response.WriteEntity(s.debugger.Threads())
|
||||
}
|
||||
|
||||
func (s *RESTServer) getThread(request *restful.Request, response *restful.Response) {
|
||||
id, err := strconv.Atoi(request.PathParameter("thread-id"))
|
||||
if err != nil {
|
||||
writeError(response, http.StatusBadRequest, "invalid thread id")
|
||||
return
|
||||
}
|
||||
|
||||
found := s.debugger.FindThread(id)
|
||||
if found == nil {
|
||||
writeError(response, http.StatusNotFound, "thread not found")
|
||||
return
|
||||
}
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(found)
|
||||
}
|
||||
|
||||
func (s *RESTServer) listPackageVars(request *restful.Request, response *restful.Response) {
|
||||
state, err := s.debugger.State()
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
current := state.CurrentThread
|
||||
if current == nil {
|
||||
writeError(response, http.StatusBadRequest, "no current thread")
|
||||
return
|
||||
}
|
||||
|
||||
filter := request.QueryParameter("filter")
|
||||
vars, err := s.debugger.PackageVariables(current.ID, filter)
|
||||
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(vars)
|
||||
}
|
||||
|
||||
func (s *RESTServer) listThreadPackageVars(request *restful.Request, response *restful.Response) {
|
||||
id, err := strconv.Atoi(request.PathParameter("thread-id"))
|
||||
if err != nil {
|
||||
writeError(response, http.StatusBadRequest, "invalid thread id")
|
||||
return
|
||||
}
|
||||
|
||||
if found := s.debugger.FindThread(id); found == nil {
|
||||
writeError(response, http.StatusNotFound, "thread not found")
|
||||
return
|
||||
}
|
||||
|
||||
filter := request.QueryParameter("filter")
|
||||
vars, err := s.debugger.PackageVariables(id, filter)
|
||||
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(vars)
|
||||
}
|
||||
|
||||
func (s *RESTServer) listRegisters(request *restful.Request, response *restful.Response) {
|
||||
state, err := s.debugger.State()
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
regs, err := s.debugger.Registers(state.CurrentThread.ID)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(regs)
|
||||
}
|
||||
|
||||
func (s *RESTServer) listLocalVars(request *restful.Request, response *restful.Response) {
|
||||
state, err := s.debugger.State()
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
vars, err := s.debugger.LocalVariables(state.CurrentThread.ID)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(vars)
|
||||
}
|
||||
|
||||
func (s *RESTServer) listFunctionArgs(request *restful.Request, response *restful.Response) {
|
||||
state, err := s.debugger.State()
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
vars, err := s.debugger.FunctionArguments(state.CurrentThread.ID)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(vars)
|
||||
}
|
||||
|
||||
func (s *RESTServer) evalSymbol(request *restful.Request, response *restful.Response) {
|
||||
symbol := request.PathParameter("symbol")
|
||||
if len(symbol) == 0 {
|
||||
writeError(response, http.StatusBadRequest, "invalid symbol")
|
||||
return
|
||||
}
|
||||
|
||||
state, err := s.debugger.State()
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
current := state.CurrentThread
|
||||
if current == nil {
|
||||
writeError(response, http.StatusBadRequest, "no current thread")
|
||||
return
|
||||
}
|
||||
|
||||
v, err := s.debugger.EvalVariableInThread(current.ID, symbol)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(v)
|
||||
}
|
||||
|
||||
func (s *RESTServer) evalThreadSymbol(request *restful.Request, response *restful.Response) {
|
||||
id, err := strconv.Atoi(request.PathParameter("thread-id"))
|
||||
if err != nil {
|
||||
writeError(response, http.StatusBadRequest, "invalid thread id")
|
||||
return
|
||||
}
|
||||
|
||||
if found := s.debugger.FindThread(id); found == nil {
|
||||
writeError(response, http.StatusNotFound, "thread not found")
|
||||
return
|
||||
}
|
||||
|
||||
symbol := request.PathParameter("symbol")
|
||||
if len(symbol) == 0 {
|
||||
writeError(response, http.StatusNotFound, "invalid symbol")
|
||||
return
|
||||
}
|
||||
|
||||
v, err := s.debugger.EvalVariableInThread(id, symbol)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(v)
|
||||
}
|
||||
|
||||
func (s *RESTServer) listSources(request *restful.Request, response *restful.Response) {
|
||||
filter := request.QueryParameter("filter")
|
||||
sources, err := s.debugger.Sources(filter)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(sources)
|
||||
}
|
||||
|
||||
func (s *RESTServer) listFunctions(request *restful.Request, response *restful.Response) {
|
||||
filter := request.QueryParameter("filter")
|
||||
funcs, err := s.debugger.Functions(filter)
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(funcs)
|
||||
}
|
||||
|
||||
func (s *RESTServer) listGoroutines(request *restful.Request, response *restful.Response) {
|
||||
gs, err := s.debugger.Goroutines()
|
||||
if err != nil {
|
||||
writeError(response, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.WriteEntity(gs)
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
package servicetest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -13,7 +12,6 @@ import (
|
||||
|
||||
"github.com/derekparker/delve/service"
|
||||
"github.com/derekparker/delve/service/api"
|
||||
"github.com/derekparker/delve/service/rest"
|
||||
"github.com/derekparker/delve/service/rpc"
|
||||
)
|
||||
|
||||
@ -31,21 +29,6 @@ func withTestClient(name string, t *testing.T, fn func(c service.Client)) {
|
||||
t.Fatalf("couldn't start listener: %s\n", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
// Test REST service
|
||||
restService := func() {
|
||||
fmt.Println("---- RUNNING TEST WITH REST CLIENT ----")
|
||||
server := rest.NewServer(&service.Config{
|
||||
Listener: listener,
|
||||
ProcessArgs: []string{protest.BuildFixture(name).Path},
|
||||
}, false)
|
||||
go server.Run()
|
||||
client := rest.NewClient(listener.Addr().String())
|
||||
defer client.Detach(true)
|
||||
fn(client)
|
||||
}
|
||||
// Test RPC service
|
||||
rpcService := func() {
|
||||
fmt.Println("---- RUNNING TEST WITH RPC CLIENT ----")
|
||||
server := rpc.NewServer(&service.Config{
|
||||
Listener: listener,
|
||||
ProcessArgs: []string{protest.BuildFixture(name).Path},
|
||||
@ -54,9 +37,6 @@ func withTestClient(name string, t *testing.T, fn func(c service.Client)) {
|
||||
client := rpc.NewClient(listener.Addr().String())
|
||||
defer client.Detach(true)
|
||||
fn(client)
|
||||
}
|
||||
rpcService()
|
||||
restService()
|
||||
}
|
||||
|
||||
func TestClientServer_exit(t *testing.T) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user