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:
Derek Parker 2015-06-24 08:08:48 -05:00
parent 6817bfc2ba
commit 7c8fd02685
5 changed files with 16 additions and 905 deletions

@ -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", &regs)
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},
@ -55,9 +38,6 @@ func withTestClient(name string, t *testing.T, fn func(c service.Client)) {
defer client.Detach(true)
fn(client)
}
rpcService()
restService()
}
func TestClientServer_exit(t *testing.T) {
withTestClient("continuetestprog", t, func(c service.Client) {