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:
parent
b815284199
commit
7ababb79f6
23 changed files with 1110 additions and 1101 deletions
|
@ -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)
|
||||
}
|
|
@ -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, ®, 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
|
||||
}
|
||||
|
|
|
@ -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(), ®)
|
||||
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(), ®)
|
||||
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(), ®)
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue