Skip to main content

Front End

Open Match Front End Edgegap's tutorial (basic)#

The Front End component of Open Match is the shield between your players and Open Match. Every player's request will pass by it. You can validate any data in this service. Make sure that all the data you receive are valid.

Prerequisites#

Create a new Go project#

Every Open Match service should be in its project. Create a new folder. In this folder, run the following command. (<MODULE_NAME> is the go module name you have chosen)

Create a REST API using ECHO#

The first thing that we need for our front end service is a REST API. We decided to go with a REST API instead of gRPC protocol for the front end service because we thought it would be simpler

In this tutorial, we will use the framework ECHO for our REST API since it's lightweight and fast. You could use any other framework if you want.

Let's start by installing the dependencies for ECHO and Open Match

go get -u github.com/labstack/echo/...
go get -u open-match.dev/open-match/pkg/pb

Now, in your main.go file, you can add the following code. This will create a web server that will listen on port 51504

package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
const (
// Link inside kubernetes
openMatchFrontendService = "open-match-frontend:50504"
)
func main() {
e := echo.New()
// How to extract IP
e.IPExtractor = echo.ExtractIPDirect()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Open Match Front End Edgegap's tutorial (basic)")
})
e.Logger.Fatal(e.Start(":51504"))
}

You can try it by starting your app with go run ./main.go and go to http://localhost:51504/

Create an Open Match ticket#

Open Match works using tickets. To let your game request a ticket, we will create an API endpoint.

Let's add a API endpoint HTTP POST on /v1/tickets. This endpoint will receive a JSON body that contains the data for the matchmaker. In this tutorial, we will keep it simple. We will receive a game mode (mode.ranked, mode.casual, mode.private). Your main function should look like this.

func main() {
e := echo.New()
// How to extract IP
e.IPExtractor = echo.ExtractIPDirect()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Open Match Front End Edgegap's tutorial (basic)")
})
// Create a ticket
e.POST("/v1/tickets", createTicket)
e.Logger.Fatal(e.Start(":51504"))
}

Now, we will create the function createTicket. In this function, we will extract the ticket's data from the request, send the data to Open Match Front End service via gRPC and send back the ticket that has been created.

// TicketRequestModel represent the model tath we should receive for our create ticket endpoint
type TicketRequestModel struct {
Mode string
}
// Create a ticket by communicating with Open Match core Front End service
func createTicket(echoContext echo.Context) error {
log.Println("Creating ticket...")
// Get The player IP. This will be used later to make a call at Arbitrium (Edgegap's solution)
echoServer := echoContext.Echo()
request := echoContext.Request()
playerIP := echoServer.IPExtractor(request)
userTicketRequest := TicketRequestModel{}
// Bind the request JSON body to our model
err := echoContext.Bind(&userTicketRequest)
if err != nil {
panic("Request Payload didn't match TicketRequestModel attributes")
}
service := getFrontendServiceClient()
req := &pb.CreateTicketRequest{
Ticket: &pb.Ticket{
SearchFields: &pb.SearchFields{
// Tags can support multiple values but for simplicity, the demo function
// assumes only single mode selection per Ticket.
Tags: []string{
userTicketRequest.Mode,
},
},
Extensions: map[string]*any.Any{
// Adding player IP to create the game server later using Arbitrium (Edgegap's solution)
// You can add other values in extensions. Those values will be ignored by Open Match. They are meant tu use by the developper.
// Find all valid type here: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf
"playerIp": {
TypeUrl: "type.googleapis.com/google.protobuf.StringValue",
Value: []byte(playerIP),
},
},
},
}
ticket, err := service.CreateTicket(context.Background(), req)
if err != nil {
log.Printf("Was not able to create a ticket, err: %s\n", err.Error())
}
return echoContext.JSON(http.StatusOK, ticket)
}
// Get a object that can communicate with Open Match Front End service.
func getFrontendServiceClient() pb.FrontendServiceClient {
conn, err := grpc.Dial(
openMatchFrontendService, grpc.WithInsecure(),
)
if err != nil {
panic(fmt.Sprintf("Could not dial Open Match Frontend service via gRPC, err: %v", err.Error()))
}
return pb.NewFrontendServiceClient(conn)
}

Don't forget to change your import!

import (
"context"
"fmt"
"log"
"net/http"
"github.com/golang/protobuf/ptypes/any"
"github.com/labstack/echo/v4"
"google.golang.org/grpc"
"open-match.dev/open-match/pkg/pb"
)

Now we can test our new endpoint. We will use Postman. Let's start our app try it.

go run ./main.go

Request Body

{
"mode": "mode.casual"
}

Notes#

At this point, if you send a request to create a ticket, you should have this error on the server side.

Was not able to create a ticket, err: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial tcp: lookup open-match-frontend: no such host"

This means that your application was not able to contact the OpenMatch Front End service. It's normal. The service is not exposed outside of the Kubernetes cluster. If you want to try it now, you can expose the service and change the variable openMatchFrontendService by 127.0.0.1:50504. Keep in mind that this is TEMPORARY.

To expose the service run

kubectl port-forward --namespace open-match service/open-match-frontend 50504:50504

Restart your app.

Now, if you send a request, everything should be fine, and your ticket should be created.

Get a ticket#

Let's add a API endpoint HTTP GET on /v1/tickets/:ticketId. This endpoint will return the data about a ticket with a given ID. Your main function should look like this.

func main() {
e := echo.New()
// How to extract IP
e.IPExtractor = echo.ExtractIPDirect()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Open Match Front End Edgegap's tutorial (basic)")
})
// Create a ticket
e.POST("/v1/tickets", createTicket)
// Get a ticket
e.GET("/v1/tickets/:ticketId", getTicket)
e.Logger.Fatal(e.Start(":51504"))
}

Now, we will create the function getTicket. In this function, we will fetch the ticket from OpenMatch Front End service via gRPC and send it back to the client.

func getTicket(echoContext echo.Context) error {
ticketID := echoContext.Param("ticketId")
service := getFrontendServiceClient()
req := &pb.GetTicketRequest{
TicketId: ticketID,
}
ticket, err := service.GetTicket(context.Background(), req)
if err != nil {
log.Printf("Was not able to get a ticket, err: %s\n", err.Error())
return echo.NewHTTPError(http.StatusNotFound, "Resource not found")
}
return echoContext.JSON(http.StatusOK, ticket)
}

Now we can test our new endpoint. We will use Postman. Let's start our app try it.

go run ./main.go

Notes#

At this point, if you send a request to get a ticket, you should have this error on the server-side.

Was not able to get a ticket, err: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial tcp: lookup open-match-frontend: no such host"

This means that your application was not able to contact the OpenMatch Front End service. It's normal. The service is not exposed outside of the Kubernetes cluster. If you want to try it now, you can expose the service and change the variable openMatchFrontendService by 127.0.0.1:50504. Keep in mind that this is TEMPORARY.

To expose the service run

kubectl port-forward --namespace open-match service/open-match-frontend 50504:50504

Restart your app.

Now, if you send a request, everything should be fine, and your ticket should be returned if it exists.

Delete a ticket#

Let's add a API endpoint HTTP DELETE on /v1/tickets/:ticketId. This endpoint will delete the ticket if it exists. Your main function should look like this.

func main() {
e := echo.New()
// How to extract IP
e.IPExtractor = echo.ExtractIPDirect()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Open Match Front End Edgegap's tutorial (basic)")
})
// Create a ticket
e.POST("/v1/tickets", createTicket)
// Get a ticket
e.GET("/v1/tickets/:ticketId", getTicket)
// Delete a ticket
e.DELETE("/v1/tickets/:ticketId", deleteTicket)
e.Logger.Fatal(e.Start(":51504"))
}

Now, we will create the function deleteTicket. In this function, we will delete the ticket from the OpenMatch Front End service via gRPC and send it back to the client.

func deleteTicket(echoContext echo.Context) error {
ticketID := echoContext.Param("ticketId")
service := getFrontendServiceClient()
req := &pb.DeleteTicketRequest{
TicketId: ticketID,
}
_, err := service.DeleteTicket(context.Background(), req)
if err != nil {
fmt.Printf("Was not able to delete a ticket, err: %s\n", err.Error())
return echo.NewHTTPError(http.StatusNotFound, "Resource not found")
}
return echoContext.JSON(http.StatusOK, pb.Ticket{Id: ticketID})
}

Now we can test our new endpoint. We will use Postman. Let's start our app try it.

go run ./main.go

Notes#

At this point, if you send a request to delete a ticket, you should have this error on the server-side.

Was not able to delete a ticket, err: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial tcp: lookup open-match-frontend: no such host"

This means that your application was not able to contact the OpenMatch Front End service. It's normal. The service is not exposed outside of the Kubernetes cluster. If you want to try it now, you can expose the service and change the variable openMatchFrontendService by 127.0.0.1:50504. Keep in mind that this is TEMPORARY.

To expose the service run

kubectl port-forward --namespace open-match service/open-match-frontend 50504:50504

Restart your app.

Now, if you send a request, everything should be fine and your ticket should be deleted if it exists.

Containerize your service#

Before the Containerization, don't forget to change back openMatchFrontendService to open-match-frontend:50504 if you changed it.

Containerization is powerful. You can learn more about it on Edgegap's documentation or Docker. Our container will use golang:1.15 for its base image.

Create a file named Dockerfile and write this inside.

FROM golang:1.15
COPY . /go/src/app
WORKDIR /go/src/app
RUN go get -d -v /go/src/app
RUN go install -v /go/src/app
WORKDIR /go/bin
# To see your build name, go in your go.mod file and look at your module name.
# The first line should be -> module A/.../<BUILD_NAME>
CMD ["<BUILD_NAME>"]

You can now build your image using. (If you use Edgegap's harbor, <REPOSITORY_MAME> is harbor.edgegap.com/<YOUR_PROJECT>)

docker build -t <REPOSITORY_MAME>/edgegap-om-tuto-frontend:tuto .

You can run it using, but without all the order services, it will not work.

docker run -it --name om-tuto-frontend <REPOSITORY_MAME>/edgegap-om-tuto-frontend:tuto

Now push your image on your Docker repository. (You may have to use Docker login)

docker push <REPOSITORY_MAME>/edgegap-om-tuto-frontend:tuto

Conclusion#

You now have a basic Open Match Front End REST API service that can be called from your game. This is not production-ready, however you should be able to grasp the basic of Open Match Front End. You also created a Docker image and pushed it to your repository.