Add caddy caddy-grpc as a reverse proxy plug-in
Project Address: https://github.com/yhyddr/caddy-grpc
Foreword
The last time we learn how to extend their desired plug-in in the Caddy. The blog provides a general framework only. This time, let's plug in accordance with specific caddy-grpc
learning.
Select the reason for it is that it itself is a separate application, where it made a plug of a Caddy. Perhaps you have a well-designed to further understanding of the Caddy.
Plug role
The purpose of the plug-and Improbable-eng / grpc-web / go / grpcwebproxy the same purpose, but as a Caddy middleware plug rather than stand-alone Go application.
The role of this project, what is it?
This is a small reverse proxy, you can use gRPC-Web protocol support existing gRPC server and its public function, allows the use of gRPC service from the browser. feature:
- Structured record (log that is) proxy requests to stdout (standard output)
- Can debug the HTTP port (default port
8080
)- Prometheus monitoring agent request (
/metrics
in the debugger on the endpoint)- The Request (
/debug/requests
) and a connection end point tracking/debug/events
( )- TLS 1.2 Service (default port
8443
):
- It has enabled client certificate authentication options
- Security (plain text) and TLS gRPC back-end connectivity:
- CA certificate is used to connect customizable
In fact it means that, to do this a reverse proxy server middleware caddy.
use
When you need to, you can
example.com
grpc localhost:9090
The first line example.com is the host name / address of the site to be served. The second line is called a grpc instruction, wherein a backend gRPC can service endpoint addresses (i.e., in the example localhost: 9090). (Note: The above configuration by default TLS 1.2-to-back gRPC Service)
Caddyfile grammar
grpc backend_addr {
backend_is_insecure
backend_tls_noverify
backend_tls_ca_files path_to_ca_file1 path_to_ca_file2
}
backend_is_insecure
By default, the agent will use TLS to connect to the back-end, but if the back-end service in clear text, you need to add this option
backend_tls_noverify
By default, to verify TLS backend. If you do not validate, you need to add this option
backend_tls_ca_files
PEM backend authentication certificate for a certificate chain paths (separated by a comma). If empty, will use the host host CA chain.
Source
Directory Structure
caddy-grpc
├── LICENSE
├── README.md
├── proxy // 代理 grpc proxy 的功能实现
│ ├── DOC.md
│ ├── LICENSE.txt
│ ├── README.md
│ ├── codec.go
│ ├── director.go
│ ├── doc.go
│ └── handler.go
├── server.go // Handle 逻辑文件
└── setup.go // 安装文件
Setup.go
In order to write plug our last point of view, if you do not remember, see: How to add plug-in extensions to caddy
First look at the files installed setup.go
init func
func init() {
caddy.RegisterPlugin("grpc", caddy.Plugin{
ServerType: "http",
Action: setup,
})
}
May know, the plug-in registration is http server, called grpc
setup func
Then we see the most important setup function, using the method just mentioned, is responsible for analyzing exactly what it caddyfile of options. It will also analyze the directive referred to Caddy's own controller to configure this plugin
// setup configures a new server middleware instance.
func setup(c *caddy.Controller) error {
for c.Next() {
var s server
if !c.Args(&s.backendAddr) { //loads next argument into backendAddr and fail if none specified
return c.ArgErr()
}
tlsConfig := &tls.Config{}
tlsConfig.MinVersion = tls.VersionTLS12
s.backendTLS = tlsConfig
s.backendIsInsecure = false
//check for more settings in Caddyfile
for c.NextBlock() {
switch c.Val() {
case "backend_is_insecure":
s.backendIsInsecure = true
case "backend_tls_noverify":
s.backendTLS = buildBackendTLSNoVerify()
case "backend_tls_ca_files":
t, err := buildBackendTLSFromCAFiles(c.RemainingArgs())
if err != nil {
return err
}
s.backendTLS = t
default:
return c.Errf("unknown property '%s'", c.Val())
}
}
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
s.next = next
return s
})
}
return nil
}
We note still c.Next () hands, used to read the configuration file, in fact here, it reads the grpc the token and the next step
Then we see that reading is followed grpc listen address.
if !c.Args(&s.backendAddr) { //loads next argument into backendAddr and fail if none specified
return c.ArgErr()
}
It corresponds exactly to the configuration of this caddyfile grpc localhost:9090
Note c.Next (), c.Args (), c.NextBlock (), are read caddyfile functions in the configuration, the caddy we called token
Also note tls configuration Mentioned earlier, the service is a service opening tls 1.2
tlsConfig := &tls.Config{}
tlsConfig.MinVersion = tls.VersionTLS12
s.backendTLS = tlsConfig
s.backendIsInsecure = false
- Then read the configuration mentioned above caddyfile syntax
//check for more settings in Caddyfile
for c.NextBlock() {
switch c.Val() {
case "backend_is_insecure":
s.backendIsInsecure = true
case "backend_tls_noverify":
s.backendTLS = buildBackendTLSNoVerify()
case "backend_tls_ca_files":
t, err := buildBackendTLSFromCAFiles(c.RemainingArgs())
if err != nil {
return err
}
s.backendTLS = t
default:
return c.Errf("unknown property '%s'", c.Val())
}
}
It can be seen through c.NextBlock()
to each token of the new analysis, different configuration after use c.Val () read.
- Finally, do not forget we need to add it to go the whole caddy middleware
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
s.next = next
return s
})
server.go
struct
First check the core structure of this plug-in. That is what data is stored
type server struct {
backendAddr string
next httpserver.Handler
backendIsInsecure bool
backendTLS *tls.Config
wrappedGrpc *grpcweb.WrappedGrpcServer
}
- backendAddr is listening address grpc services
- next is the next process Handler plug-in
- backendIsInsecure and back-office services are backendTLS whether a different security policy is enabled.
- wrappedGrpc is the key to the plugin, which implements a grpcweb protocol, to allow the service to be grpc browser.
serveHTTP
Our last article, which is the second most important part, serveHTTP represents the realization of specific functions. The last time we only used to deliver content to the logical next Handle
func (g gizmoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
return g.next.ServeHTTP(w, r)
}
Now we look at the grpc what logic it added.
// ServeHTTP satisfies the httpserver.Handler interface.
func (s server) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
//dial Backend
opt := []grpc.DialOption{}
opt = append(opt, grpc.WithCodec(proxy.Codec()))
if s.backendIsInsecure {
opt = append(opt, grpc.WithInsecure())
} else {
opt = append(opt, grpc.WithTransportCredentials(credentials.NewTLS(s.backendTLS)))
}
backendConn, err := grpc.Dial(s.backendAddr, opt...)
if err != nil {
return s.next.ServeHTTP(w, r)
}
director := func(ctx context.Context, fullMethodName string) (context.Context, *grpc.ClientConn, error) {
md, _ := metadata.FromIncomingContext(ctx)
return metadata.NewOutgoingContext(ctx, md.Copy()), backendConn, nil
}
grpcServer := grpc.NewServer(
grpc.CustomCodec(proxy.Codec()), // needed for proxy to function.
grpc.UnknownServiceHandler(proxy.TransparentHandler(director)),
/*grpc_middleware.WithUnaryServerChain(
grpc_logrus.UnaryServerInterceptor(logger),
grpc_prometheus.UnaryServerInterceptor,
),
grpc_middleware.WithStreamServerChain(
grpc_logrus.StreamServerInterceptor(logger),
grpc_prometheus.StreamServerInterceptor,
),*/ //middleware should be a config setting or 3rd party middleware plugins like for caddyhttp
)
// gRPC-Web compatibility layer with CORS configured to accept on every
wrappedGrpc := grpcweb.WrapServer(grpcServer, grpcweb.WithCorsForRegisteredEndpointsOnly(false))
wrappedGrpc.ServeHTTP(w, r)
return 0, nil
}
- The first is the configuration section grpc, if you know grpc, you'll know which is used to grpc client options configuration. Here Codec codec adds a different security policies and options for our clients.
//dial Backend
opt := []grpc.DialOption{}
opt = append(opt, grpc.WithCodec(proxy.Codec()))
if s.backendIsInsecure {
opt = append(opt, grpc.WithInsecure())
} else {
opt = append(opt, grpc.WithTransportCredentials(credentials.NewTLS(s.backendTLS)))
}
backendConn, err := grpc.Dial(s.backendAddr, opt...)
if err != nil {
return s.next.ServeHTTP(w, r)
}
- Then the server is set up options grpc
director := func(ctx context.Context, fullMethodName string) (context.Context, *grpc.ClientConn, error) {
md, _ := metadata.FromIncomingContext(ctx)
return metadata.NewOutgoingContext(ctx, md.Copy()), backendConn, nil
}
grpcServer := grpc.NewServer(
grpc.CustomCodec(proxy.Codec()), // needed for proxy to function.
grpc.UnknownServiceHandler(proxy.TransparentHandler(director)),
/*grpc_middleware.WithUnaryServerChain(
grpc_logrus.UnaryServerInterceptor(logger),
grpc_prometheus.UnaryServerInterceptor,
),
grpc_middleware.WithStreamServerChain(
grpc_logrus.StreamServerInterceptor(logger),
grpc_prometheus.StreamServerInterceptor,
),*/ //middleware should be a config setting or 3rd party middleware plugins like for caddyhttp
)
- Finally, use grpcweb.WrapServer to implement web services calls
// gRPC-Web compatibility layer with CORS configured to accept on every
wrappedGrpc := grpcweb.WrapServer(grpcServer, grpcweb.WithCorsForRegisteredEndpointsOnly(false))
wrappedGrpc.ServeHTTP(w, r)
Proxy
He noted that the use of this function proxy.TransparentHandler handler.go proxy is defined in the above. GRPC used to implement proxy services. Here comes to realize about the interaction gRPC, focusing on transport stream Client and Server, and little to do with this article, are interested can understand down.
Epilogue
Think about this as a Caddy plugin brings what?
Is not the moment to get a lot of configuration can be extended?
Instead Caddy in want of some of the plug-in function to do the very beginning of the project, said independent application.
If you are doing HTTP service, Caddy also envious of some of the features and its ecology, such as access to it.
It also involves the grpc-web, if you are interested, you can learn about extended
grpc-web client implementations/examples:
reference
Caddy: https://github.com/caddyserver/caddy
how to write middleware: https://github.com/caddyserver/caddy/wiki/Writing-a-Plugin:-HTTP-Middleware
Caddy plug-GRPC: HTTPS: / /github.com/pieterlouw/caddy-grpc