caddy & grpc (3) adding a reverse proxy plug-in for caddy

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 ( /metricsin 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
}
  1. 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

  2. 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

  1. Note c.Next (), c.Args (), c.NextBlock (), are read caddyfile functions in the configuration, the caddy we called token

  2. 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
  1. 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.

  1. 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

Following the second step.

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:

Vue.js
GopherJS

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

Guess you like

Origin www.cnblogs.com/abser/p/11332253.html