Overview
This example shows how to serve multiple communication protocols by a single port. The buf command generates the boilerplate code. The connect RPC protocol serves the gRPc function. Also, http/1 GET and Gin, which satisfy the standard ServeHTTP function, are served.
Prerequirement
These tools and a protcol is used.
Example Dir Structure
Example dirs and files are here.
Makefile
go.mod
go.sum
api/
buf.yml ... buf configuration files
buf.gen.yml
proto/hello/v1
hello.proto ... grpc and protobuf definitions
rpc/hello/v1/ ... generated codes
hello.pb.go
v1connect/
hello.connect.go
cmd/
main.go ... example code
package main
import (
"context"
"fmt"
"log"
"net/http"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"connectrpc.com/connect"
"github.com/gin-gonic/gin"
helo "example.com/connrpcothr/rpc/hello/v1"
conn "example.com/connrpcothr/rpc/hello/v1/v1connect"
)
type Server struct{}
func (s *Server) Greet(c context.Context, in *connect.Request[helo.Request]) (*connect.Response[helo.Response], error) {
return connect.NewResponse(&helo.Response{Msg: fmt.Sprintf("Greet :%v", in.Msg.Text)}), nil
}
func main() {
mux := http.NewServeMux()
// connectrpc
s := &Server{}
path, hdr := conn.NewHeloServiceHandler(s)
mux.Handle(path, hdr)
log.Printf("HeloService Path: %v", path)
// std
ptn := "GET /std/n/{num}"
mux.Handle(ptn, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
txt := r.PathValue("num")
w.Header().Set("Content-Type", "plain/text")
w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf("std: %s", txt)))
}))
log.Printf("Std : %s", ptn)
// gin
egn := gin.Default()
api := egn.Group("api/v1")
api.GET("/h/:text", func(c *gin.Context) {
txt := c.Param("text")
c.JSON(http.StatusOK, gin.H{
"msg": fmt.Sprintf("gin: %s", txt),
})
})
mux.Handle("/", egn)
if err := http.ListenAndServe("localhost:8080", h2c.NewHandler(mux, &http2.Server{})); err != nil {
log.Fatalf("down: %v", err)
}
}
Confirmation
This example code confirms whether it’s possible to serve each protocol such as connectrpc, grpc and rest.
gRPC proto
syntax = "proto3";
package hello.v1;
option go_package = "example.com/connrpcothr/rpc/hello/v1";
message Request {
string text = 1;
}
message Response {
string msg = 1;
}
service HeloService {
rpc Greet(Request) returns (Response) {}
}
Connectrpc
type Server struct{}
func (s *Server) Greet(c context.Context, in *connect.Request[helo.Request])
(*connect.Response[helo.Response], error) {
return connect.NewResponse(&helo.Response{Msg: fmt.Sprintf("Greet :%v", in.Msg.Text)}), nil
}
Standard Package
ptn := "GET /std/n/{num}"
mux.Handle(ptn, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
txt := r.PathValue("num")
w.Header().Set("Content-Type", "plain/text")
w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf("std: %s", txt)))
}))
log.Printf("Std : %s", ptn)
Gin
egn := gin.Default()
api := egn.Group("api/v1")
api.GET("/h/:text", func(c *gin.Context) {
txt := c.Param("text")
c.JSON(http.StatusOK, gin.H{
"msg": fmt.Sprintf("gin: %s", txt),
})
})
mux.Handle("/", egn)
Call Each Endpoints
To call each endpoint, boot the exmple server.
go run ./cmd/main.go
2025/01/11 19:25:38 HeloService Path: /hello.v1.HeloService/
2025/01/11 19:25:38 Std : GET /std/n/{num}
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /api/v1/h/:text --> main.main.func2 (3 handlers)
- Call Connectrcp by curl
curl -H 'Content-Type: application/json' -d '{"text": "aaa"}' localhost:8080/hello.v1.HeloService/Greet {"msg":"Greet :aaa"}
- Call Connectrcp by grpcurl
grpcurl -protoset <(buf build --path api/proto/hello/v1/hello.proto -o -) -plaintext -d '{"text":"aaa"}' localhost:8080 hello.v1.HeloService/Greet { "msg": "Greet :aaa" }
- Call standard http handler by curl
curl -v localhost:8080/std/n/wew * Host localhost:8080 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:8080... * connect to ::1 port 8080 from ::1 port 60073 failed: Connection refused * Trying 127.0.0.1:8080... * Connected to localhost (127.0.0.1) port 8080 > GET /std/n/wew HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/1.1 200 OK < Content-Type: plain/text < Date: Sun, 12 Jan 2025 01:43:29 GMT < Content-Length: 8 < * Connection #0 to host localhost left intact std: wew
- Call Gin endoint by curl
curl -v localhost:8080/api/v1/h/123 * Host localhost:8080 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:8080... * connect to ::1 port 8080 from ::1 port 60083 failed: Connection refused * Trying 127.0.0.1:8080... * Connected to localhost (127.0.0.1) port 8080 > GET /api/v1/h/123 HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/1.1 200 OK < Content-Type: application/json; charset=utf-8 < Date: Sun, 12 Jan 2025 01:44:50 GMT < Content-Length: 18 < * Connection #0 to host localhost left intact {"msg":"gin: 123"}