Migrate to gRPC rather than REST with swagger

Will also be adding in a RESTful endpoint to the server as well so it can consume both types
This commit is contained in:
Marc Di Luzio 2020-06-12 22:51:18 +01:00
parent b815284199
commit 7ababb79f6
23 changed files with 1110 additions and 1101 deletions

View file

@ -2,7 +2,6 @@ package main
import (
"context"
"fmt"
"log"
"net"
"os"
@ -34,20 +33,20 @@ func (a *accountantServer) Register(ctx context.Context, in *accounts.RegisterIn
log.Printf("Registering account: %s\n", in.Name)
if _, err := a.accountant.RegisterAccount(in.Name); err != nil {
log.Printf("Error: %s\n", err)
return &accounts.RegisterResponse{Success: false, Error: fmt.Sprintf("error registering account: %s", err)}, nil
return nil, err
}
// Save out the accounts
if err := persistence.Save("accounts", a.accountant); err != nil {
log.Printf("Error: %s\n", err)
return &accounts.RegisterResponse{Success: false, Error: fmt.Sprintf("failed to save accounts: %s", err)}, nil
return nil, err
}
return &accounts.RegisterResponse{Success: true}, nil
return &accounts.RegisterResponse{}, nil
}
// AssignData assigns a key value pair to an account
func (a *accountantServer) AssignValue(_ context.Context, in *accounts.DataKeyValue) (*accounts.Response, error) {
func (a *accountantServer) AssignValue(_ context.Context, in *accounts.DataKeyValue) (*accounts.DataKeyResponse, error) {
a.sync.RLock()
defer a.sync.RUnlock()
@ -56,10 +55,10 @@ func (a *accountantServer) AssignValue(_ context.Context, in *accounts.DataKeyVa
err := a.accountant.AssignData(in.Account, in.Key, in.Value)
if err != nil {
log.Printf("Error: %s\n", err)
return &accounts.Response{Success: false, Error: err.Error()}, nil
return nil, err
}
return &accounts.Response{Success: true}, nil
return &accounts.DataKeyResponse{}, nil
}
@ -73,10 +72,10 @@ func (a *accountantServer) GetValue(_ context.Context, in *accounts.DataKey) (*a
data, err := a.accountant.GetValue(in.Account, in.Key)
if err != nil {
log.Printf("Error: %s\n", err)
return &accounts.DataResponse{Success: false, Error: err.Error()}, nil
return nil, err
}
return &accounts.DataResponse{Success: true, Value: data}, nil
return &accounts.DataResponse{Value: data}, nil
}
@ -117,7 +116,7 @@ func main() {
// Serve the RPC server
log.Printf("Serving accountant on %s\n", address)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to server gRPC: %s", err)
log.Fatalf("failed to serve gRPC: %s", err)
}
// Save out the accountant data

View file

@ -1,91 +0,0 @@
// +build integration
package internal
import (
"os"
"testing"
"github.com/google/uuid"
"github.com/mdiluz/rove/pkg/game"
"github.com/mdiluz/rove/pkg/rove"
"github.com/stretchr/testify/assert"
)
const (
defaultAddress = "localhost:8080"
)
var serv = func() rove.Server {
var address = os.Getenv("ROVE_SERVER_ADDRESS")
if len(address) == 0 {
address = defaultAddress
}
return rove.Server(address)
}()
func TestServer_Status(t *testing.T) {
status, err := serv.Status()
assert.NoError(t, err)
assert.True(t, status.Ready)
assert.NotZero(t, len(status.Version))
}
func TestServer_Register(t *testing.T) {
d1 := rove.RegisterData{
Name: uuid.New().String(),
}
_, err := serv.Register(d1)
assert.NoError(t, err)
d2 := rove.RegisterData{
Name: uuid.New().String(),
}
_, err = serv.Register(d2)
assert.NoError(t, err)
_, err = serv.Register(d1)
assert.Error(t, err)
}
func TestServer_Command(t *testing.T) {
d1 := rove.RegisterData{
Name: uuid.New().String(),
}
_, err := serv.Register(d1)
assert.NoError(t, err)
c := rove.CommandData{
Commands: []game.Command{
{
Command: game.CommandMove,
Bearing: "N",
Duration: 1,
},
},
}
_, err = serv.Command(d1.Name, c)
assert.NoError(t, err)
}
func TestServer_Radar(t *testing.T) {
d1 := rove.RegisterData{
Name: uuid.New().String(),
}
_, err := serv.Register(d1)
assert.NoError(t, err)
_, err = serv.Radar(d1.Name)
assert.NoError(t, err)
}
func TestServer_Rover(t *testing.T) {
d1 := rove.RegisterData{
Name: uuid.New().String(),
}
_, err := serv.Register(d1)
assert.NoError(t, err)
_, err = serv.Rover(d1.Name)
assert.NoError(t, err)
}

View file

@ -2,69 +2,26 @@ package internal
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
"github.com/golang/protobuf/ptypes/empty"
"github.com/google/uuid"
"github.com/mdiluz/rove/pkg/accounts"
"github.com/mdiluz/rove/pkg/game"
"github.com/mdiluz/rove/pkg/rove"
"github.com/mdiluz/rove/pkg/version"
"google.golang.org/grpc"
)
// Handler describes a function that handles any incoming request and can respond
type Handler func(*Server, map[string]string, io.ReadCloser) (interface{}, error)
// Route defines the information for a single path->function route
type Route struct {
path string
method string
handler Handler
}
// Routes is an array of all the Routes
var Routes = []Route{
{
path: "/status",
method: http.MethodGet,
handler: HandleStatus,
},
{
path: "/register",
method: http.MethodPost,
handler: HandleRegister,
},
{
path: "/{account}/command",
method: http.MethodPost,
handler: HandleCommand,
},
{
path: "/{account}/radar",
method: http.MethodGet,
handler: HandleRadar,
},
{
path: "/{account}/rover",
method: http.MethodGet,
handler: HandleRover,
},
}
// HandleStatus handles the /status request
func HandleStatus(s *Server, vars map[string]string, b io.ReadCloser) (interface{}, error) {
// Simply return the current server status
response := rove.StatusResponse{
func (s *Server) Status(context.Context, *empty.Empty) (*rove.StatusResponse, error) {
response := &rove.StatusResponse{
Ready: true,
Version: version.Version,
Tick: s.tick,
Tick: int32(s.tick),
}
// TODO: Verify the accountant is up and ready too
// If there's a schedule, respond with it
if len(s.schedule.Entries()) > 0 {
response.NextTick = s.schedule.Entries()[0].Next.Format("15:04:05")
@ -73,31 +30,15 @@ func HandleStatus(s *Server, vars map[string]string, b io.ReadCloser) (interface
return response, nil
}
// HandleRegister handles /register endpoint
func HandleRegister(s *Server, vars map[string]string, b io.ReadCloser) (interface{}, error) {
var response = rove.RegisterResponse{}
// Decode the registration info, verify it and register the account
var data rove.RegisterData
err := json.NewDecoder(b).Decode(&data)
if err != nil {
log.Printf("Failed to decode json: %s\n", err)
return BadRequestError{Error: err.Error()}, nil
} else if len(data.Name) == 0 {
return BadRequestError{Error: "cannot register empty name"}, nil
func (s *Server) Register(ctx context.Context, req *rove.RegisterRequest) (*empty.Empty, error) {
if len(req.Name) == 0 {
return nil, fmt.Errorf("empty account name")
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
reg := accounts.RegisterInfo{Name: data.Name}
if acc, err := s.accountant.Register(ctx, &reg, grpc.WaitForReady(true)); err != nil {
return nil, fmt.Errorf("gRPC failed to contact accountant: %s", err)
} else if !acc.Success {
return BadRequestError{Error: acc.Error}, nil
if _, err := s.accountant.Register(ctx, &accounts.RegisterInfo{Name: req.Name}, grpc.WaitForReady(true)); err != nil {
return nil, err
} else if _, _, err := s.SpawnRoverForAccount(data.Name); err != nil {
} else if _, _, err := s.SpawnRoverForAccount(req.Name); err != nil {
return nil, fmt.Errorf("failed to spawn rover for account: %s", err)
} else if err := s.SaveWorld(); err != nil {
@ -105,66 +46,48 @@ func HandleRegister(s *Server, vars map[string]string, b io.ReadCloser) (interfa
}
log.Printf("register response:%+v\n", response)
return response, nil
return &empty.Empty{}, nil
}
// HandleSpawn will spawn the player entity for the associated account
func HandleCommand(s *Server, vars map[string]string, b io.ReadCloser) (interface{}, error) {
var response = rove.CommandResponse{}
func (s *Server) Rover(ctx context.Context, req *rove.RoverRequest) (*rove.RoverResponse, error) {
response := &rove.RoverResponse{}
if len(req.Account) == 0 {
return nil, fmt.Errorf("empty account name")
id := vars["account"]
// Decode the commands, verify them and the account, and execute the commands
var data rove.CommandData
if err := json.NewDecoder(b).Decode(&data); err != nil {
log.Printf("Failed to decode json: %s\n", err)
return BadRequestError{Error: err.Error()}, nil
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
key := accounts.DataKey{Account: id, Key: "rover"}
if len(id) == 0 {
return BadRequestError{Error: "no account ID provided"}, nil
} else if resp, err := s.accountant.GetValue(ctx, &key); err != nil {
} else if resp, err := s.accountant.GetValue(ctx, &accounts.DataKey{Account: req.Account, Key: "rover"}); err != nil {
return nil, fmt.Errorf("gRPC failed to contact accountant: %s", err)
} else if !resp.Success {
return BadRequestError{Error: resp.Error}, nil
} else if id, err := uuid.Parse(resp.Value); err != nil {
return nil, fmt.Errorf("account had invalid rover ID: %s", resp.Value)
} else if err := s.world.Enqueue(id, data.Commands...); err != nil {
return BadRequestError{Error: err.Error()}, nil
} else if attrib, err := s.world.RoverAttributes(id); err != nil {
return nil, fmt.Errorf("error getting rover attributes: %s", err)
} else {
response = &rove.RoverResponse{
Name: attrib.Name,
Position: &rove.Vector{
X: int32(attrib.Pos.X),
Y: int32(attrib.Pos.Y),
},
}
}
log.Printf("command response \taccount:%s\tresponse:%+v\n", id, response)
return response, nil
}
// HandleRadar handles the radar request
func HandleRadar(s *Server, vars map[string]string, b io.ReadCloser) (interface{}, error) {
var response = rove.RadarResponse{}
func (s *Server) Radar(ctx context.Context, req *rove.RadarRequest) (*rove.RadarResponse, error) {
if len(req.Account) == 0 {
return nil, fmt.Errorf("empty account name")
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
id := vars["account"]
key := accounts.DataKey{Account: id, Key: "rover"}
if len(id) == 0 {
return BadRequestError{Error: "no account ID provided"}, nil
response := &rove.RadarResponse{}
} else if resp, err := s.accountant.GetValue(ctx, &key); err != nil {
resp, err := s.accountant.GetValue(ctx, &accounts.DataKey{Account: req.Account, Key: "rover"})
if err != nil {
return nil, fmt.Errorf("gRPC failed to contact accountant: %s", err)
}
} else if !resp.Success {
return BadRequestError{Error: resp.Error}, nil
} else if id, err := uuid.Parse(resp.Value); err != nil {
if id, err := uuid.Parse(resp.Value); err != nil {
return nil, fmt.Errorf("account had invalid rover ID: %s", resp.Value)
} else if attrib, err := s.world.RoverAttributes(id); err != nil {
@ -175,40 +98,38 @@ func HandleRadar(s *Server, vars map[string]string, b io.ReadCloser) (interface{
} else {
response.Tiles = radar
response.Range = attrib.Range
response.Range = int32(attrib.Range)
}
log.Printf("radar response \taccount:%s\tresponse:%+v\n", id, response)
return response, nil
}
// HandleRover handles the rover request
func HandleRover(s *Server, vars map[string]string, b io.ReadCloser) (interface{}, error) {
var response = rove.RoverResponse{}
func (s *Server) Commands(ctx context.Context, req *rove.CommandsRequest) (*empty.Empty, error) {
if len(req.Account) == 0 {
return nil, fmt.Errorf("empty account")
}
resp, err := s.accountant.GetValue(ctx, &accounts.DataKey{Account: req.Account, Key: "rover"})
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
id := vars["account"]
key := accounts.DataKey{Account: id, Key: "rover"}
if len(id) == 0 {
return BadRequestError{Error: "no account ID provided"}, nil
if err != nil {
return nil, err
}
} else if resp, err := s.accountant.GetValue(ctx, &key); err != nil {
return nil, fmt.Errorf("gRPC failed to contact accountant: %s", err)
} else if !resp.Success {
return BadRequestError{Error: resp.Error}, nil
} else if id, err := uuid.Parse(resp.Value); err != nil {
id, err := uuid.Parse(resp.Value)
if err != nil {
return nil, fmt.Errorf("account had invalid rover ID: %s", resp.Value)
} else if attrib, err := s.world.RoverAttributes(id); err != nil {
return nil, fmt.Errorf("error getting rover attributes: %s", err)
} else {
response.Attributes = attrib
}
log.Printf("rover response \taccount:%s\tresponse:%+v\n", id, response)
return response, nil
var cmds []game.Command
for _, c := range req.Commands {
cmds = append(cmds, game.Command{
Bearing: c.Bearing,
Command: c.Command,
Duration: int(c.Duration)})
}
if err := s.world.Enqueue(id, cmds...); err != nil {
return nil, err
}
return &empty.Empty{}, nil
}

View file

@ -1,202 +0,0 @@
// +build integration
package internal
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"path"
"testing"
"github.com/google/uuid"
"github.com/mdiluz/rove/pkg/accounts"
"github.com/mdiluz/rove/pkg/atlas"
"github.com/mdiluz/rove/pkg/game"
"github.com/mdiluz/rove/pkg/rove"
"github.com/mdiluz/rove/pkg/vector"
"github.com/stretchr/testify/assert"
)
func TestHandleStatus(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/status", nil)
response := httptest.NewRecorder()
s := NewServer()
s.Initialise(true)
s.router.ServeHTTP(response, request)
assert.Equal(t, http.StatusOK, response.Code)
var status rove.StatusResponse
err := json.NewDecoder(response.Body).Decode(&status)
assert.NoError(t, err)
if status.Ready != true {
t.Errorf("got false for /status")
}
if len(status.Version) == 0 {
t.Errorf("got empty version info")
}
}
func TestHandleRegister(t *testing.T) {
data := rove.RegisterData{Name: uuid.New().String()}
b, err := json.Marshal(data)
if err != nil {
t.Error(err)
}
request, _ := http.NewRequest(http.MethodPost, "/register", bytes.NewReader(b))
response := httptest.NewRecorder()
s := NewServer()
s.Initialise(true)
s.router.ServeHTTP(response, request)
assert.Equal(t, http.StatusOK, response.Code)
var status rove.RegisterResponse
err = json.NewDecoder(response.Body).Decode(&status)
assert.NoError(t, err)
}
func TestHandleCommand(t *testing.T) {
name := uuid.New().String()
s := NewServer()
s.Initialise(false) // Leave the world empty with no obstacles
reg := accounts.RegisterInfo{Name: name}
acc, err := s.accountant.Register(context.Background(), &reg)
assert.NoError(t, err)
assert.NotNil(t, acc)
assert.True(t, acc.Success, acc.Error)
assert.NoError(t, err, "Error registering account")
// Spawn the rover rover for the account
_, inst, err := s.SpawnRoverForAccount(name)
assert.NoError(t, s.world.WarpRover(inst, vector.Vector{}))
attribs, err := s.world.RoverAttributes(inst)
assert.NoError(t, err, "Couldn't get rover position")
data := rove.CommandData{
Commands: []game.Command{
{
Command: game.CommandMove,
Bearing: "N",
Duration: 1,
},
},
}
b, err := json.Marshal(data)
assert.NoError(t, err, "Error marshalling data")
request, _ := http.NewRequest(http.MethodPost, path.Join("/", name, "/command"), bytes.NewReader(b))
response := httptest.NewRecorder()
s.router.ServeHTTP(response, request)
assert.Equal(t, http.StatusOK, response.Code)
var status rove.CommandResponse
err = json.NewDecoder(response.Body).Decode(&status)
assert.NoError(t, err)
attrib, err := s.world.RoverAttributes(inst)
assert.NoError(t, err, "Couldn't get rover attribs")
// Tick the command queues to progress the move command
s.world.EnqueueAllIncoming()
s.world.ExecuteCommandQueues()
attribs2, err := s.world.RoverAttributes(inst)
assert.NoError(t, err, "Couldn't get rover position")
attribs.Pos.Add(vector.Vector{X: 0.0, Y: attrib.Speed * 1}) // Should have moved north by the speed and duration
assert.Equal(t, attribs.Pos, attribs2.Pos, "Rover should have moved by bearing")
}
func TestHandleRadar(t *testing.T) {
name := uuid.New().String()
s := NewServer()
s.Initialise(false) // Spawn a clean world
reg := accounts.RegisterInfo{Name: name}
acc, err := s.accountant.Register(context.Background(), &reg)
assert.NoError(t, err)
assert.True(t, acc.Success, acc.Error)
assert.NoError(t, err, "Error registering account")
// Spawn the rover rover for the account
attrib, id, err := s.SpawnRoverForAccount(name)
assert.NoError(t, err)
// Warp this rover to 0,0
assert.NoError(t, s.world.WarpRover(id, vector.Vector{}))
// Explicity set a few nearby tiles
wallPos1 := vector.Vector{X: 0, Y: -1}
wallPos2 := vector.Vector{X: 1, Y: 1}
rockPos := vector.Vector{X: 1, Y: 3}
emptyPos := vector.Vector{X: -2, Y: -3}
assert.NoError(t, s.world.Atlas.SetTile(wallPos1, atlas.TileWall))
assert.NoError(t, s.world.Atlas.SetTile(wallPos2, atlas.TileWall))
assert.NoError(t, s.world.Atlas.SetTile(rockPos, atlas.TileRock))
assert.NoError(t, s.world.Atlas.SetTile(emptyPos, atlas.TileEmpty))
request, _ := http.NewRequest(http.MethodGet, path.Join("/", name, "/radar"), nil)
response := httptest.NewRecorder()
s.router.ServeHTTP(response, request)
assert.Equal(t, http.StatusOK, response.Code)
var status rove.RadarResponse
err = json.NewDecoder(response.Body).Decode(&status)
assert.NoError(t, err)
scope := attrib.Range*2 + 1
radarOrigin := vector.Vector{X: -attrib.Range, Y: -attrib.Range}
// Make sure the rover tile is correct
assert.Equal(t, atlas.TileRover, status.Tiles[len(status.Tiles)/2])
// Check our other tiles
wallPos1.Add(radarOrigin.Negated())
wallPos2.Add(radarOrigin.Negated())
rockPos.Add(radarOrigin.Negated())
emptyPos.Add(radarOrigin.Negated())
assert.Equal(t, atlas.TileWall, status.Tiles[wallPos1.X+wallPos1.Y*scope])
assert.Equal(t, atlas.TileWall, status.Tiles[wallPos2.X+wallPos2.Y*scope])
assert.Equal(t, atlas.TileRock, status.Tiles[rockPos.X+rockPos.Y*scope])
assert.Equal(t, atlas.TileEmpty, status.Tiles[emptyPos.X+emptyPos.Y*scope])
}
func TestHandleRover(t *testing.T) {
name := uuid.New().String()
s := NewServer()
s.Initialise(true)
reg := accounts.RegisterInfo{Name: name}
acc, err := s.accountant.Register(context.Background(), &reg)
assert.NoError(t, err)
assert.True(t, acc.Success, acc.Error)
// Spawn one rover for the account
attribs, _, err := s.SpawnRoverForAccount(name)
assert.NoError(t, err)
request, _ := http.NewRequest(http.MethodGet, path.Join("/", name, "/rover"), nil)
response := httptest.NewRecorder()
s.router.ServeHTTP(response, request)
assert.Equal(t, http.StatusOK, response.Code)
var status rove.RoverResponse
err = json.NewDecoder(response.Body).Decode(&status)
assert.NoError(t, err)
if attribs != status.Attributes {
t.Errorf("Missmatched attributes: %+v, !=%+v", attribs, status.Attributes)
}
}

View file

@ -2,20 +2,17 @@ package internal
import (
"context"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
"sync"
"time"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/mdiluz/rove/pkg/accounts"
"github.com/mdiluz/rove/pkg/game"
"github.com/mdiluz/rove/pkg/persistence"
"github.com/mdiluz/rove/pkg/rove"
"github.com/robfig/cron"
"google.golang.org/grpc"
)
@ -40,10 +37,9 @@ type Server struct {
accountant accounts.AccountantClient
clientConn *grpc.ClientConn
// HTTP server
listener net.Listener
server *http.Server
router *mux.Router
// gRPC server
netListener net.Listener
grpcServ *grpc.Server
// Config settings
address string
@ -85,13 +81,10 @@ func OptionTick(minutes int) ServerOption {
// NewServer sets up a new server
func NewServer(opts ...ServerOption) *Server {
router := mux.NewRouter().StrictSlash(true)
// Set up the default server
s := &Server{
address: "",
persistence: EphemeralData,
router: router,
schedule: cron.New(),
}
@ -100,9 +93,6 @@ func NewServer(opts ...ServerOption) *Server {
o(s)
}
// Set up the server object
s.server = &http.Server{Addr: s.address, Handler: s.router}
// Start small, we can grow the world later
s.world = game.NewWorld(4, 8)
@ -133,18 +123,14 @@ func (s *Server) Initialise(fillWorld bool) (err error) {
return err
}
// Set up the handlers
for _, route := range Routes {
s.router.HandleFunc(route.path, s.wrapHandler(route.method, route.handler))
// Set up the RPC server and register
s.netListener, err = net.Listen("tcp", s.address)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s.grpcServ = grpc.NewServer()
rove.RegisterRoverServerServer(s.grpcServ, s)
// Start the listen
log.Printf("Listening on %s\n", s.server.Addr)
if s.listener, err = net.Listen("tcp", s.server.Addr); err != nil {
return err
}
s.address = s.listener.Addr().String()
return nil
}
@ -178,9 +164,10 @@ func (s *Server) Run() {
log.Printf("First server tick scheduled for %s\n", s.schedule.Entries()[0].Next.Format("15:04:05"))
}
// Serve the http requests
if err := s.server.Serve(s.listener); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
// Serve the RPC server
log.Printf("Serving rove on %s\n", s.address)
if err := s.grpcServ.Serve(s.netListener); err != nil && err != grpc.ErrServerStopped {
log.Fatalf("failed to serve gRPC: %s", err)
}
}
@ -189,12 +176,8 @@ func (s *Server) Stop() error {
// Stop the cron
s.schedule.Stop()
// Try and shut down the http server
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.server.Shutdown(ctx); err != nil {
return err
}
// Stop the gRPC
s.grpcServ.Stop()
// Close the accountant connection
if err := s.clientConn.Close(); err != nil {
@ -206,7 +189,7 @@ func (s *Server) Stop() error {
// Close waits until the server is finished and closes up shop
func (s *Server) Close() error {
// Wait until the server has shut down
// Wait until the world has shut down
s.sync.Wait()
// Save and return
@ -253,40 +236,6 @@ type BadRequestError struct {
Error string `json:"error"`
}
// wrapHandler wraps a request handler in http checks
func (s *Server) wrapHandler(method string, handler Handler) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// Log the request
log.Printf("%s\t%s\n", r.Method, r.RequestURI)
vars := mux.Vars(r)
// Verify the method, call the handler, and encode the return
if r.Method != method {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
val, err := handler(s, vars, r.Body)
if err != nil {
log.Printf("Failed to handle http request: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
} else if _, ok := val.(BadRequestError); ok {
w.WriteHeader(http.StatusBadRequest)
}
if err := json.NewEncoder(w).Encode(val); err != nil {
log.Printf("Failed to encode reply to json: %s", err)
w.WriteHeader(http.StatusInternalServerError)
} else {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
}
}
}
// SpawnRoverForAccount spawns the rover rover for an account
func (s *Server) SpawnRoverForAccount(account string) (game.RoverAttributes, uuid.UUID, error) {
if inst, err := s.world.SpawnRover(); err != nil {
@ -297,9 +246,9 @@ func (s *Server) SpawnRoverForAccount(account string) (game.RoverAttributes, uui
} else {
keyval := accounts.DataKeyValue{Account: account, Key: "rover", Value: inst.String()}
resp, err := s.accountant.AssignValue(context.Background(), &keyval)
if err != nil || !resp.Success {
log.Printf("Failed to assign rover to account, %s, %s", err, resp.Error)
_, err := s.accountant.AssignValue(context.Background(), &keyval)
if err != nil {
log.Printf("Failed to assign rover to account, %s", err)
// Try and clear up the rover
if err := s.world.DestroyRover(inst); err != nil {

View file

@ -7,10 +7,14 @@ import (
"io/ioutil"
"os"
"path"
"time"
"github.com/golang/protobuf/ptypes/empty"
"github.com/mdiluz/rove/pkg/game"
"github.com/mdiluz/rove/pkg/rove"
"github.com/mdiluz/rove/pkg/version"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
var USAGE = ""
@ -90,7 +94,13 @@ func InnerMain(command string) error {
}
// Set up the server
var server = rove.Server(config.Host)
clientConn, err := grpc.Dial(config.Host, grpc.WithInsecure())
if err != nil {
return err
}
var client = rove.NewRoverServerClient(clientConn)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Grab the account
var account = config.Accounts[config.Host]
@ -98,7 +108,7 @@ func InnerMain(command string) error {
// Handle all the commands
switch command {
case "status":
response, err := server.Status()
response, err := client.Status(ctx, &empty.Empty{})
switch {
case err != nil:
return err
@ -114,10 +124,10 @@ func InnerMain(command string) error {
if len(*name) == 0 {
return fmt.Errorf("must set name with -name")
}
d := rove.RegisterData{
d := rove.RegisterRequest{
Name: *name,
}
_, err := server.Register(d)
_, err := client.Register(ctx, &d)
switch {
case err != nil:
return err
@ -128,11 +138,12 @@ func InnerMain(command string) error {
}
case "move":
d := rove.CommandData{
Commands: []game.Command{
d := rove.CommandsRequest{
Account: account,
Commands: []*rove.Command{
{
Command: game.CommandMove,
Duration: *duration,
Duration: int32(*duration),
Bearing: *bearing,
},
},
@ -142,7 +153,7 @@ func InnerMain(command string) error {
return err
}
_, err := server.Command(account, d)
_, err := client.Commands(ctx, &d)
switch {
case err != nil:
return err
@ -152,11 +163,12 @@ func InnerMain(command string) error {
}
case "radar":
dat := rove.RadarRequest{Account: account}
if err := verifyId(account); err != nil {
return err
}
response, err := server.Radar(account)
response, err := client.Radar(ctx, &dat)
switch {
case err != nil:
return err
@ -167,17 +179,18 @@ func InnerMain(command string) error {
}
case "rover":
req := rove.RoverRequest{Account: account}
if err := verifyId(account); err != nil {
return err
}
response, err := server.Rover(account)
response, err := client.Rover(ctx, &req)
switch {
case err != nil:
return err
default:
fmt.Printf("attributes: %+v\n", response.Attributes)
fmt.Printf("attributes: %+v\n", response)
}
case "config":
fmt.Printf("host: %s\taccount: %s\n", config.Host, account)