1. 程式人生 > 其它 >golang GRPC 和http 服務同時使用

golang GRPC 和http 服務同時使用

golang微服務元件 gokit
  • 服務三層架構解析
    • Transport 傳輸 這是通訊的入口,你將使用這一層去建立你的介面,讓你的介面去接收呼叫者的引數
    • Endpoint 端點 終點 這是一個終點也是一個起點,它將讓你去處理request以及response
    • Service 服務 這裡才是你介面真正的開始,接收由Endpoint帶來的資料,並處理它,返回它

目錄結構解析

 --cmd
   -- main.go
 --endpoints
    -- endpoint.go
 --services
    -- service.go
 -- transports
   --pb
     -- hello.proto
     -- complie.bat
   -- transport.go
 -- test
   hello_test.go

hello.proto

syntax = "proto3";

package pb;

option go_package = ".;pb";

service HelloService {
  rpc Greeting(GreetingRequest) returns (GreetingResponse) {}
}

message GreetingRequest {
  string name = 1;
}

message GreetingResponse {
  string message = 2;
  string err = 3;
}
complie.bat
protoc --go_out=. --go-grpc_out=. hello.proto

資料的入口【協議平面】 接受原始資料 transport.go decode/encode

package transports

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"sanbox/gokit/transports/pb"
	"strings"
)

// ErrEmpty is returned when an input name is empty.
var ErrEmpty = errors.New("empty name")

func DecodeGreetingRequest(_ context.Context, r *http.Request) (interface{}, error) {
	var request pb.GreetingRequest
	switch r.Method {
	case http.MethodGet:
		vars := r.URL.Query()
		if name, ok := vars["name"]; !ok || len(name) != 1 {
			return nil, ErrEmpty
		} else {
			request.Name = name[0]
		}
	case http.MethodPost:
		if strings.HasPrefix(r.Header["Content-Type"][0], "application/json") {
			if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
				return nil, err
			}
		}
	}
	return request, nil
}

func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	return json.NewEncoder(w).Encode(response)
}

func DecodeGRPCGreetingRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
	req := grpcReq.(*pb.GreetingRequest)
	return pb.GreetingRequest{Name: req.Name}, nil
}

func EncodeGRPCResponse(_ context.Context, response interface{}) (interface{}, error) {
	resp := response.(pb.GreetingResponse)
	return &pb.GreetingResponse{Message: resp.Message, Err: resp.Err}, nil
}

請求處理層【控制平面】 通過transprot結構化的資料化滿足介面的資料 request / response endpoint.go

package endpoints

import (
	"context"
	"github.com/go-kit/kit/endpoint"
	"sanbox/gokit/services"
	"sanbox/gokit/transports/pb"
)

type GreetingRequest struct {
	Name string `json:"name"`
}

type GreetingResponse struct {
	Message string `json:"message"`
	Err     string `json:"err,omitempty"` // errors don't define JSON marshaling
}

func MakeGreetingEndpoint(svc services.HelloService) endpoint.Endpoint {
	return func(_ context.Context, request interface{}) (interface{}, error) {
		req := request.(pb.GreetingRequest)
		message, err := svc.Greeting(req.Name)
		if err != nil {
			return pb.GreetingResponse{Message: message, Err: err.Error()}, nil
		}
		return pb.GreetingResponse{Message: message, Err: ""}, nil
	}
}


服務層【資料平面】 Endpoint帶來的資料
package services

import (
	"context"
	"fmt"
	grpctransport "github.com/go-kit/kit/transport/grpc"
	"sanbox/gokit/entry"
	"sanbox/gokit/transports/pb"
	"time"
)

// HelloServiceInterface provides greeting message.
type HelloServiceInterface interface {
	Greeting(string) (string, error)
}

type HelloService struct{}

func (HelloService) Greeting(s string) (string, error) {
	if s == "" {
		return fmt.Sprintf("Hello guest, your visit time is %s.", time.Now().Format("2006-01-02 15:04:05")), entry.ErrEmpty
	}
	return fmt.Sprintf("Hello %s, your visit time is %s.", s, time.Now().Format("2006-01-02 15:04:05")), nil
}

type HelloGRPCServer struct {
	GreetingHandler grpctransport.Handler
	pb.UnimplementedHelloServiceServer
}

func (s *HelloGRPCServer) Greeting(ctx context.Context, req *pb.GreetingRequest) (*pb.GreetingResponse, error) {
	_, res, err := s.GreetingHandler.ServeGRPC(ctx, req)
	if err != nil {
		return nil, err
	}
	return res.(*pb.GreetingResponse), nil
}

程式入口 mian.go

package main

import (
	grpctransport "github.com/go-kit/kit/transport/grpc"
	httptransport "github.com/go-kit/kit/transport/http"
	"google.golang.org/grpc"
	"log"
	"net"
	"net/http"
	"os"
	"sanbox/gokit/endpoints"
	"sanbox/gokit/services"
	"sanbox/gokit/transports"
	"sanbox/gokit/transports/pb"
)

func main() {
	go runWEB()
	runGPRC()
}

func runWEB() {
	svc := services.HelloService{}
	greetingHandler := httptransport.NewServer(
		endpoints.MakeGreetingEndpoint(svc),
		transports.DecodeGreetingRequest,
		transports.EncodeResponse,
	)

	http.Handle("/greeting", greetingHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func runGPRC() {
	grpcListener, err := net.Listen("tcp", ":8081")
	if err != nil {
		os.Exit(1)
	}

	grpcServer := grpc.NewServer()

	pb.RegisterHelloServiceServer(grpcServer, &services.HelloGRPCServer{
		GreetingHandler: grpctransport.NewServer(
			endpoints.MakeGreetingEndpoint(services.HelloService{}),
			transports.DecodeGRPCGreetingRequest,
			transports.EncodeGRPCResponse,
		)})

	grpcServer.Serve(grpcListener)

}

測試檔案 helle_test.go

package test

import (
	"context"
	"github.com/valyala/fasthttp"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"log"
	"sanbox/gokit/transports/pb"
	"testing"
	"time"
)

func TestHello(t *testing.T) {
	conn, err := grpc.Dial("localhost:8081", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("connect failed! error: %v", err)
	}
	defer conn.Close()
	c := pb.NewHelloServiceClient(conn)
	name := "John"
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.Greeting(ctx, &pb.GreetingRequest{Name: name})
	if err != nil {
		log.Fatalf("call failed!, error: %v", err)
	}
	log.Printf("message: %v, err: %v", r.Message, r.Err)
}

func TestHttpGet(t *testing.T) {
	code, resp, err := fasthttp.Get(nil, "http://localhost:8080/greeting?name=Jack")
	if err != nil {
		log.Fatalf("call failed!, error: %v", err)
	}

	log.Printf("message: %s \n code: %v", resp, code)
}
func TestHttpPost(t *testing.T) {
	code, resp, err := fasthttp.Post([]byte(`{"name": "Jack"}`), "http://localhost:8080/greeting", nil)
	if err != nil {
		log.Fatalf("call failed!, error: %v", err)
	}

	log.Printf("message: %s \n code: %v", resp, code)
}