mirror of
https://gitlab.com/djdietrick/docs
synced 2026-05-03 01:30:55 -04:00
Added go rpc and grpc sections
This commit is contained in:
@@ -0,0 +1,212 @@
|
|||||||
|
# gRPC
|
||||||
|
|
||||||
|
GRPC is an enhancement to RPC in that both sides don't have to be written in Go, because there are libraries for a lot of other languages. It also defines it's payload types in separate proto files so they can be shared among teams.
|
||||||
|
|
||||||
|
Documentation: https://grpc.io/docs/languages/go/basics/
|
||||||
|
|
||||||
|
## Install GRPC
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27
|
||||||
|
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Proto Files
|
||||||
|
|
||||||
|
```proto
|
||||||
|
// log.proto
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package logs;
|
||||||
|
|
||||||
|
option go_package = "/logs";
|
||||||
|
|
||||||
|
message Log{
|
||||||
|
string name = 1;
|
||||||
|
string data = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LogRequest{
|
||||||
|
Log logEntry = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LogResponse{
|
||||||
|
string result = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service LogService{
|
||||||
|
rpc WriteLog(LogRequest) returns (LogResponse);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compile Protos
|
||||||
|
|
||||||
|
Proto compiler link: https://grpc.io/docs/protoc-installation/. This should be stored somewhere in your path, preferably in your `~/go/bin` directory.
|
||||||
|
|
||||||
|
In directory with your proto file, run the below command. This will generate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative logs.proto
|
||||||
|
```
|
||||||
|
|
||||||
|
## Listener
|
||||||
|
|
||||||
|
### Install dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get google.golang.org/gprc
|
||||||
|
go get google.golang.org/protobuf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implement handlers
|
||||||
|
|
||||||
|
```go
|
||||||
|
// grpc.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"logger/data" // our db models
|
||||||
|
"logger/logs" // our proto files
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogServer struct {
|
||||||
|
logs.UnimplementedLogServiceServer
|
||||||
|
Models data.Models // db writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using code generated from proto
|
||||||
|
func (l *LogServer) WriteLog(ctx context.Context, req *logs.LogRequest) (*logs.LogResponse, error) {
|
||||||
|
input := req.GetLogEntry() // auto generated function
|
||||||
|
|
||||||
|
logEntry := data.LogEntry{
|
||||||
|
Name: input.Name,
|
||||||
|
Data: input.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.Models.LogEntry.Insert(logEntry)
|
||||||
|
if err != nil {
|
||||||
|
resp := &logs.LogResponse{
|
||||||
|
Result: "failed",
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &logs.LogResponse{
|
||||||
|
Result: "logged!",
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Config) gRPCListen() {
|
||||||
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", grpcPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to listen to gRPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := grpc.NewServer()
|
||||||
|
|
||||||
|
logs.RegisterLogServiceServer(s, &LogServer{
|
||||||
|
Models: app.Models,
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("gRPC server listening on port %s", grpcPort)
|
||||||
|
|
||||||
|
if err := s.Serve(lis); err != nil {
|
||||||
|
log.Fatalf("failed to serve gRPC: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Instantiate server
|
||||||
|
|
||||||
|
```go
|
||||||
|
// grpc.go
|
||||||
|
func (app *Config) gRPCListen() {
|
||||||
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", grpcPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to listen to gRPC: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := grpc.NewServer()
|
||||||
|
|
||||||
|
// auto generated function
|
||||||
|
logs.RegisterLogServiceServer(s, &LogServer{
|
||||||
|
Models: app.Models,
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("gRPC server listening on port %s", grpcPort)
|
||||||
|
|
||||||
|
if err := s.Serve(lis); err != nil {
|
||||||
|
log.Fatalf("failed to serve gRPC: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// main.go
|
||||||
|
go app.grpcLisen()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sender
|
||||||
|
|
||||||
|
The sender must also have access to the proto files generated by the listener. It would be a good idea to store these in a separate repo so that multiple teams can access them from a central source of truth.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log" // proto
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (app *Config) logItemViaGRPC(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var requestPayload RequestPayload
|
||||||
|
|
||||||
|
err := app.readJSON(w, r, &requestPayload)
|
||||||
|
if err != nil {
|
||||||
|
app.errorJSON(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to pass some sort of credentials to the server
|
||||||
|
conn, err := grpc.Dial("logger:50001", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
|
||||||
|
if err != nil {
|
||||||
|
app.errorJSON(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// generated in proto
|
||||||
|
client := logs.NewLogServiceClient(conn)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err = client.WriteLog(ctx, &logs.LogRequest{
|
||||||
|
LogEntry: &logs.Log{
|
||||||
|
Name: requestPayload.Log.Name,
|
||||||
|
Data: requestPayload.Log.Data,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
app.errorJSON(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := jsonResponse{
|
||||||
|
Error: false,
|
||||||
|
Message: "logged via gRPC",
|
||||||
|
}
|
||||||
|
|
||||||
|
app.writeJSON(w, http.StatusAccepted, payload)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
0
docs/go/advanced/http.md
Normal file
0
docs/go/advanced/http.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# RPC
|
||||||
|
|
||||||
|
RPC stands for Remote Procedure Calls, and allows for external Go microservices to call functions in our application directly.
|
||||||
|
|
||||||
|
## Listener
|
||||||
|
|
||||||
|
To do this, you need to define a RPCServer dummy type, and payload to receive, and a function that gets a pointer to a response string and returns an error.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type RPCServer struct{}
|
||||||
|
|
||||||
|
type RPCPayload struct {
|
||||||
|
Name string
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RPCServer) LogInfo(payload RPCPayload, resp *string) error {
|
||||||
|
collection := client.Database("logs").Collection("logs")
|
||||||
|
_, err := collection.InsertOne(context.TODO(), data.LogEntry{
|
||||||
|
Name: payload.Name,
|
||||||
|
Data: payload.Data,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error writing to mongo ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*resp = "processed payload via RPC: " + payload.Name
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
To instantiate the RPCServer, use the below syntax in your main application file. Basically you define a function that creates the connection and listens on a port. Then, you register your RPC handler type and serve the RPC server.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (app *Config) rpcListen() error {
|
||||||
|
log.Println("Starting RPC server on port " + RPC_PORT)
|
||||||
|
listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", RPC_PORT))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer listen.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
rpcConn, err := listen.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error accepting connection: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go rpc.ServeConn(rpcConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in function where you start your app...
|
||||||
|
err = rpc.Register(new(RPCServer))
|
||||||
|
go app.rpcListen()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sender
|
||||||
|
|
||||||
|
To send messages via RPC, you need to create a type for the payload that exactly matches that of the receiver.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type RPCPayload struct {
|
||||||
|
Name string
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Config) logItemViaRPC(r http.ResponseWriter, l LogPayload) {
|
||||||
|
client, err := rpc.Dial("tcp", "logger:5001")
|
||||||
|
if err != nil {
|
||||||
|
app.errorJSON(r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := RPCPayload{
|
||||||
|
Name: l.Name,
|
||||||
|
Data: l.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp string
|
||||||
|
|
||||||
|
err = client.Call("RPCServer.LogInfo", payload, &resp)
|
||||||
|
if err != nil {
|
||||||
|
app.errorJSON(r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
returnPayload := jsonResponse{
|
||||||
|
Error: false,
|
||||||
|
Message: resp,
|
||||||
|
}
|
||||||
|
|
||||||
|
app.writeJSON(r, http.StatusAccepted, returnPayload)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"text": "Go Advanced",
|
"text": "Go Advanced",
|
||||||
"items": [
|
"items": [
|
||||||
{ "text": "Testing", "link": "/go/advanced/testing" },
|
{ "text": "Testing", "link": "/go/advanced/testing" },
|
||||||
|
{"text": "HTTP", "link": "/go/advanced/http"},
|
||||||
{"text": "RPC", "link": "/go/advanced/rpc"},
|
{"text": "RPC", "link": "/go/advanced/rpc"},
|
||||||
{"text": "gRPC", "link": "/go/advanced/grpc"}
|
{"text": "gRPC", "link": "/go/advanced/grpc"}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user