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. It is also in this component that you would validate that the data received is valid.

Prerequisites#

  • Install Golang 1.16^ on your computer

Create Your project#

Every Open Match service should be in its own project. Create a new folder named front-end for the OpenMatch front-end component.

In this folder, run the following command.

go mod init frontend

And create a file named main.go where the core logic of the component will be.

Create a REST API#

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/v4
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

main.go (Override All)
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.

main.go (Replace)
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.

main.go (Add to file)
// 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, conn := getFrontendServiceClient()
// Don't forget to close the connection to prevent any memory leak!
defer conn.Close()
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 an object that can communicate with Open Match Front End service.
func getFrontendServiceClient() (pb.FrontendServiceClient, *grpc.ClientConn) {
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), conn
}

Don't forget to change your import!

main.go (Replace)
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. Let's start our app and try it.

Warning : 

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.

go run ./main.go

To test this functionality, make a POST request to http://localhost:51504/v1/tickets with the following command.

curl -X POST -H "Content-Type: application/json" -d '{"mode": "mode.casual"}' 127.0.0.1:51504/v1/tickets

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.

main.go (Replace)
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.

main.go (Add to file)
func getTicket(echoContext echo.Context) error {
ticketID := echoContext.Param("ticketId")
service, conn := getFrontendServiceClient()
// Don't forget to close the connection to prevent any memory leak!
defer conn.Close()
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. Let's start our app try it.

Warning : 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.

go run ./main.go

To test this functionality, make a GET request to http://localhostL51504/v1/tickets/<ticket id>, changing <ticket id> for an ID.

curl -X GET 127.0.0.1:51504/v1/tickets/[TICKET_ID]

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.

main.go (Replace)
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.

main.go (Add to namespace file)
func deleteTicket(echoContext echo.Context) error {
ticketID := echoContext.Param("ticketId")
service, conn := getFrontendServiceClient()
// Don't forget to close the connection to prevent any memory leak!
defer conn.Close()
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. Let's start our app try it.

Warning : 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.

go run ./main.go

To test this functionality, make a DELETE request to http://localhost:51504/v1/tickets/<ticket id> changing <ticket id> for an ID.

curl -X DELETE 127.0.0.1:51504/v1/tickets/[TICKET_ID]

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.16 for its base image.

Create a file named Dockerfile and write this inside.

FROM golang:1.16
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
CMD ["frontend"]

You can now build your image using.

docker build -t edgegap-om-tuto-frontend:tuto .

You can run it using this command, but without all the other services, it will not work.

docker run -it -p 51504:51504 --name om-tuto-frontend 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 basics of Open Match Front End. You also created a Docker image that can be used later.