mirror of
https://gitlab.com/djdietrick/docs
synced 2026-05-02 22:10: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",
|
||||
"items": [
|
||||
{ "text": "Testing", "link": "/go/advanced/testing" },
|
||||
{"text": "HTTP", "link": "/go/advanced/http"},
|
||||
{"text": "RPC", "link": "/go/advanced/rpc"},
|
||||
{"text": "gRPC", "link": "/go/advanced/grpc"}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user