diff --git a/docs/img/grpc.svg b/docs/img/grpc.svg
index 038cd13e5..3f1d53c76 100644
--- a/docs/img/grpc.svg
+++ b/docs/img/grpc.svg
@@ -1 +1,4 @@
-
\ No newline at end of file
+
+
+
+
diff --git a/docs/user-guide/grpc.md b/docs/user-guide/grpc.md
index 530dc5de5..a4bda1bc7 100644
--- a/docs/user-guide/grpc.md
+++ b/docs/user-guide/grpc.md
@@ -1,30 +1,10 @@
-# gRPC example
+# gRPC examples
-This section explains how to use Traefik as reverse proxy for gRPC application with self-signed certificates.
+## With HTTP (h2c)
-!!! warning
- As gRPC needs HTTP2, we need HTTPS certificates on Træfik.
- For exchanges with the backend, we will use h2c (HTTP2 on HTTP without TLS)
+This section explains how to use Traefik as reverse proxy for gRPC application.
-
-
-
-
-## gRPC Client certificate
-
-Generate your self-signed certificate for frontend url:
-
-```bash
-openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./frontend.key -out ./frontend.cert
-```
-
-with
-
-```
-Common Name (e.g. server FQDN or YOUR name) []: frontend.local
-```
-
-## Træfik configuration
+### Træfik configuration
At last, we configure our Træfik instance to use both self-signed certificates.
@@ -32,14 +12,9 @@ At last, we configure our Træfik instance to use both self-signed certificates.
defaultEntryPoints = ["https"]
[entryPoints]
- [entryPoints.https]
- address = ":4443"
- [entryPoints.https.tls]
- # For secure connection on frontend.local
- [[entryPoints.https.tls.certificates]]
- certFile = "./frontend.cert"
- keyFile = "./frontend.key"
-
+ [entryPoints.http]
+ address = ":80"
+ [entryPoints.http]
[api]
@@ -62,23 +37,115 @@ defaultEntryPoints = ["https"]
!!! warning
For provider with label, you will have to specify the `traefik.protocol=h2c`
-## Conclusion
+### Conclusion
-We don't need specific configuration to use gRPC in Træfik, we just need to be careful that exchanges between client and Træfik are HTTPS communications.
-For exchanges between Træfik and backend, you need to use `h2c` protocol, or use HTTPS communications to have HTTP2.
+We don't need specific configuration to use gRPC in Træfik, we just need to use `h2c` protocol, or use HTTPS communications to have HTTP2 with the backend.
-## A gRPC example in go
+## With HTTPS
-We will use the gRPC greeter example in [grpc-go](https://github.com/grpc/grpc-go/tree/master/examples/helloworld)
+This section explains how to use Traefik as reverse proxy for gRPC application with self-signed certificates.
+
+
+
+
+
+### gRPC Server certificate
+
+In order to secure the gRPC server, we generate a self-signed certificate for backend url:
+
+```bash
+openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./backend.key -out ./backend.cert
+```
+
+That will prompt for information, the important answer is:
+
+```
+Common Name (e.g. server FQDN or YOUR name) []: backend.local
+```
+
+### gRPC Client certificate
+
+Generate your self-signed certificate for frontend url:
+
+```bash
+openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./frontend.key -out ./frontend.cert
+```
+
+with
+
+```
+Common Name (e.g. server FQDN or YOUR name) []: frontend.local
+```
+
+### Træfik configuration
+
+At last, we configure our Træfik instance to use both self-signed certificates.
+
+```toml
+defaultEntryPoints = ["https"]
+
+# For secure connection on backend.local
+rootCAs = [ "./backend.cert" ]
+
+[entryPoints]
+ [entryPoints.https]
+ address = ":4443"
+ [entryPoints.https.tls]
+ # For secure connection on frontend.local
+ [[entryPoints.https.tls.certificates]]
+ certFile = "./frontend.cert"
+ keyFile = "./frontend.key"
+
+
+[api]
+
+[file]
+
+[backends]
+ [backends.backend1]
+ [backends.backend1.servers.server1]
+ # Access on backend with HTTPS
+ url = "https://backend.local:8080"
+
+
+[frontends]
+ [frontends.frontend1]
+ backend = "backend1"
+ [frontends.frontend1.routes.test_1]
+ rule = "Host:frontend.local"
+```
+
+!!! warning
+ With some backends, the server URLs use the IP, so you may need to configure `insecureSkipVerify` instead of the `rootCAS` to activate HTTPS without hostname verification.
+
+### A gRPC example in go (modify for https)
+
+We use the gRPC greeter example in [grpc-go](https://github.com/grpc/grpc-go/tree/master/examples/helloworld)
+
+!!! warning
+ In order to use this gRPC example, we need to modify it to use HTTPS
+
+So we modify the "gRPC server example" to use our own self-signed certificate:
-We can keep the Server example as is with the h2c protocol
```go
// ...
-lis, err := net.Listen("tcp", port)
+
+// Read cert and key file
+BackendCert, _ := ioutil.ReadFile("./backend.cert")
+BackendKey, _ := ioutil.ReadFile("./backend.key")
+
+// Generate Certificate struct
+cert, err := tls.X509KeyPair(BackendCert, BackendKey)
if err != nil {
- log.Fatalf("failed to listen: %v", err)
+ log.Fatalf("failed to parse certificate: %v", err)
}
-var s *grpc.Server = grpc.NewServer()
+
+// Create credentials
+creds := credentials.NewServerTLSFromCert(&cert)
+
+// Use Credentials in gRPC server options
+serverOption := grpc.Creds(creds)
+var s *grpc.Server = grpc.NewServer(serverOption)
defer s.Stop()
pb.RegisterGreeterServer(s, &server{})
@@ -87,10 +154,6 @@ err := s.Serve(lis)
// ...
```
-!!! warning
- In order to use this gRPC example, we need to modify it to use HTTPS
-
-
Next we will modify gRPC Client to use our Træfik self-signed certificate:
```go
diff --git a/h2c/h2c.go b/h2c/h2c.go
new file mode 100644
index 000000000..d01d88f83
--- /dev/null
+++ b/h2c/h2c.go
@@ -0,0 +1,475 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package h2c implements the h2c part of HTTP/2.
+//
+// The h2c protocol is the non-TLS secured version of HTTP/2 which is not
+// available from net/http.
+package h2c
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/base64"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "net/textproto"
+ "os"
+ "strings"
+
+ "github.com/containous/traefik/log"
+
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/hpack"
+ "golang.org/x/net/lex/httplex"
+)
+
+var (
+ http2VerboseLogs bool
+)
+
+func init() {
+ e := os.Getenv("GODEBUG")
+ if strings.Contains(e, "http2debug=1") || strings.Contains(e, "http2debug=2") {
+ http2VerboseLogs = true
+ }
+ http2VerboseLogs = true
+}
+
+// Server implements net.Handler and enables h2c. Users who want h2c just need
+// to provide an http.Server.
+type Server struct {
+ *http.Server
+ originalHandler http.Handler
+}
+
+// Serve Put a middleware around the original handler to handle h2c
+func (s Server) Serve(l net.Listener) error {
+ originalHandler := s.Server.Handler
+ if originalHandler == nil {
+ originalHandler = http.DefaultServeMux
+ }
+ s.Server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "PRI" && r.URL.Path == "*" && r.Proto == "HTTP/2.0" {
+ if http2VerboseLogs {
+ log.Debugf("Attempting h2c with prior knowledge.")
+ }
+ conn, err := initH2CWithPriorKnowledge(w)
+ if err != nil {
+ if http2VerboseLogs {
+ log.Debugf("Error h2c with prior knowledge: %v", err)
+ }
+ return
+ }
+ defer conn.Close()
+ h2cSrv := &http2.Server{}
+ h2cSrv.ServeConn(conn, &http2.ServeConnOpts{Handler: originalHandler})
+ return
+ }
+ if conn, err := h2cUpgrade(w, r); err == nil {
+ defer conn.Close()
+ h2cSrv := &http2.Server{}
+ h2cSrv.ServeConn(conn, &http2.ServeConnOpts{Handler: originalHandler})
+ return
+ }
+ originalHandler.ServeHTTP(w, r)
+ })
+ return s.Server.Serve(l)
+}
+
+// initH2CWithPriorKnowledge implements creating a h2c connection with prior
+// knowledge (Section 3.4) and creates a net.Conn suitable for http2.ServeConn.
+// All we have to do is look for the client preface that is suppose to be part
+// of the body, and reforward the client preface on the net.Conn this function
+// creates.
+func initH2CWithPriorKnowledge(w http.ResponseWriter) (net.Conn, error) {
+ hijacker, ok := w.(http.Hijacker)
+ if !ok {
+ return nil, errors.New("hijack not supported")
+ }
+ conn, rw, err := hijacker.Hijack()
+ if err != nil {
+ return nil, fmt.Errorf("hijack failed: %v", err)
+ }
+
+ expectedBody := "SM\r\n\r\n"
+
+ buf := make([]byte, len(expectedBody))
+ n, err := io.ReadFull(rw, buf)
+
+ if bytes.Equal(buf[0:n], []byte(expectedBody)) {
+ c := &rwConn{
+ Conn: conn,
+ Reader: io.MultiReader(bytes.NewBuffer([]byte(http2.ClientPreface)), rw),
+ BufWriter: rw.Writer,
+ }
+ return c, nil
+ }
+
+ conn.Close()
+ if http2VerboseLogs {
+ log.Printf(
+ "Missing the request body portion of the client preface. Wanted: %v Got: %v",
+ []byte(expectedBody),
+ buf[0:n],
+ )
+ }
+ return nil, errors.New("invalid client preface")
+}
+
+// drainClientPreface reads a single instance of the HTTP/2 client preface from
+// the supplied reader.
+func drainClientPreface(r io.Reader) error {
+ var buf bytes.Buffer
+ prefaceLen := int64(len([]byte(http2.ClientPreface)))
+ n, err := io.CopyN(&buf, r, prefaceLen)
+ if err != nil {
+ return err
+ }
+ if n != prefaceLen || buf.String() != http2.ClientPreface {
+ return fmt.Errorf("Client never sent: %s", http2.ClientPreface)
+ }
+ return nil
+}
+
+// h2cUpgrade establishes a h2c connection using the HTTP/1 upgrade (Section 3.2).
+func h2cUpgrade(w http.ResponseWriter, r *http.Request) (net.Conn, error) {
+ if !isH2CUpgrade(r.Header) {
+ return nil, errors.New("non-conforming h2c headers")
+ }
+
+ // Initial bytes we put into conn to fool http2 server
+ initBytes, _, err := convertH1ReqToH2(r)
+ if err != nil {
+ return nil, err
+ }
+
+ hijacker, ok := w.(http.Hijacker)
+ if !ok {
+ return nil, errors.New("hijack not supported")
+ }
+ conn, rw, err := hijacker.Hijack()
+ if err != nil {
+ return nil, fmt.Errorf("hijack failed: %v", err)
+ }
+
+ rw.Write([]byte("HTTP/1.1 101 Switching Protocols\r\n" +
+ "Connection: Upgrade\r\n" +
+ "Upgrade: h2c\r\n\r\n"))
+ rw.Flush()
+
+ // A conforming client will now send an H2 client preface which need to drain
+ // since we already sent this.
+ if err := drainClientPreface(rw); err != nil {
+ return nil, err
+ }
+
+ c := &rwConn{
+ Conn: conn,
+ Reader: io.MultiReader(initBytes, rw),
+ BufWriter: newSettingsAckSwallowWriter(rw.Writer),
+ }
+ return c, nil
+}
+
+// convert the data contained in the HTTP/1 upgrade request into the HTTP/2
+// version in byte form.
+func convertH1ReqToH2(r *http.Request) (*bytes.Buffer, []http2.Setting, error) {
+ h2Bytes := bytes.NewBuffer([]byte((http2.ClientPreface)))
+ framer := http2.NewFramer(h2Bytes, nil)
+ settings, err := getH2Settings(r.Header)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if err := framer.WriteSettings(settings...); err != nil {
+ return nil, nil, err
+ }
+
+ headerBytes, err := getH2HeaderBytes(r, getMaxHeaderTableSize(settings))
+ if err != nil {
+ return nil, nil, err
+ }
+
+ maxFrameSize := int(getMaxFrameSize(settings))
+ needOneHeader := len(headerBytes) < maxFrameSize
+ err = framer.WriteHeaders(http2.HeadersFrameParam{
+ StreamID: 1,
+ BlockFragment: headerBytes,
+ EndHeaders: needOneHeader,
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for i := maxFrameSize; i < len(headerBytes); i += maxFrameSize {
+ if len(headerBytes)-i > maxFrameSize {
+ if err := framer.WriteContinuation(1,
+ false, // endHeaders
+ headerBytes[i:maxFrameSize]); err != nil {
+ return nil, nil, err
+ }
+ } else {
+ if err := framer.WriteContinuation(1,
+ true, // endHeaders
+ headerBytes[i:]); err != nil {
+ return nil, nil, err
+ }
+ }
+ }
+
+ return h2Bytes, settings, nil
+}
+
+// getMaxFrameSize returns the SETTINGS_MAX_FRAME_SIZE. If not present default
+// value is 16384 as specified by RFC 7540 Section 6.5.2.
+func getMaxFrameSize(settings []http2.Setting) uint32 {
+ for _, setting := range settings {
+ if setting.ID == http2.SettingMaxFrameSize {
+ return setting.Val
+ }
+ }
+ return 16384
+}
+
+// getMaxHeaderTableSize returns the SETTINGS_HEADER_TABLE_SIZE. If not present
+// default value is 4096 as specified by RFC 7540 Section 6.5.2.
+func getMaxHeaderTableSize(settings []http2.Setting) uint32 {
+ for _, setting := range settings {
+ if setting.ID == http2.SettingHeaderTableSize {
+ return setting.Val
+ }
+ }
+ return 4096
+}
+
+// bufWriter is a Writer interface that also has a Flush method.
+type bufWriter interface {
+ io.Writer
+ Flush() error
+}
+
+// rwConn implements net.Conn but overrides Read and Write so that reads and
+// writes are forwarded to the provided io.Reader and bufWriter.
+type rwConn struct {
+ net.Conn
+ io.Reader
+ BufWriter bufWriter
+}
+
+// Read forwards reads to the underlying Reader.
+func (c *rwConn) Read(p []byte) (int, error) {
+ return c.Reader.Read(p)
+}
+
+// Write forwards writes to the underlying bufWriter and immediately flushes.
+func (c *rwConn) Write(p []byte) (int, error) {
+ n, err := c.BufWriter.Write(p)
+ if err := c.BufWriter.Flush(); err != nil {
+ return 0, err
+ }
+ return n, err
+}
+
+// settingsAckSwallowWriter is a writer that normally forwards bytes to it's
+// underlying Writer, but swallows the first SettingsAck frame that it sees.
+type settingsAckSwallowWriter struct {
+ Writer *bufio.Writer
+ buf []byte
+ didSwallow bool
+}
+
+// newSettingsAckSwallowWriter returns a new settingsAckSwallowWriter.
+func newSettingsAckSwallowWriter(w *bufio.Writer) *settingsAckSwallowWriter {
+ return &settingsAckSwallowWriter{
+ Writer: w,
+ buf: make([]byte, 0),
+ didSwallow: false,
+ }
+}
+
+// Write implements io.Writer interface. Normally forwards bytes to w.Writer,
+// except for the first Settings ACK frame that it sees.
+func (w *settingsAckSwallowWriter) Write(p []byte) (int, error) {
+ if !w.didSwallow {
+ w.buf = append(w.buf, p...)
+ // Process all the frames we have collected into w.buf
+ for {
+ // Append until we get full frame header which is 9 bytes
+ if len(w.buf) < 9 {
+ break
+ }
+ // Check if we have collected a whole frame.
+ fh, err := http2.ReadFrameHeader(bytes.NewBuffer(w.buf))
+ if err != nil {
+ // Corrupted frame, fail current Write
+ return 0, err
+ }
+ fSize := fh.Length + 9
+ if uint32(len(w.buf)) < fSize {
+ // Have not collected whole frame. Stop processing buf, and withhold on
+ // forward bytes to w.Writer until we get the full frame.
+ break
+ }
+
+ // We have now collected a whole frame.
+ if fh.Type == http2.FrameSettings && fh.Flags.Has(http2.FlagSettingsAck) {
+ // If Settings ACK frame, do not forward to underlying writer, remove
+ // bytes from w.buf, and record that we have swallowed Settings Ack
+ // frame.
+ w.didSwallow = true
+ w.buf = w.buf[fSize:]
+ continue
+ }
+
+ // Not settings ack frame. Forward bytes to w.Writer.
+ if _, err := w.Writer.Write(w.buf[:fSize]); err != nil {
+ // Couldn't forward bytes. Fail current Write.
+ return 0, err
+ }
+ w.buf = w.buf[fSize:]
+ }
+ return len(p), nil
+ }
+ return w.Writer.Write(p)
+}
+
+// Flush calls w.Writer.Flush.
+func (w *settingsAckSwallowWriter) Flush() error {
+ return w.Writer.Flush()
+}
+
+// isH2CUpgrade returns true if the header properly request an upgrade to h2c
+// as specified by Section 3.2.
+func isH2CUpgrade(h http.Header) bool {
+ return httplex.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Upgrade")], "h2c") &&
+ httplex.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Connection")], "HTTP2-Settings")
+}
+
+// getH2Settings returns the []http2.Setting that are encoded in the
+// HTTP2-Settings header.
+func getH2Settings(h http.Header) ([]http2.Setting, error) {
+ vals, ok := h[textproto.CanonicalMIMEHeaderKey("HTTP2-Settings")]
+ if !ok {
+ return nil, errors.New("missing HTTP2-Settings header")
+ }
+ if len(vals) != 1 {
+ return nil, fmt.Errorf("expected 1 HTTP2-Settings. Got: %v", vals)
+ }
+ settings, err := decodeSettings(vals[0])
+ if err != nil {
+ return nil, fmt.Errorf("Invalid HTTP2-Settings: %q", vals[0])
+ }
+ return settings, nil
+}
+
+// decodeSettings decodes the base64url header value of the HTTP2-Settings
+// header. RFC 7540 Section 3.2.1.
+func decodeSettings(headerVal string) ([]http2.Setting, error) {
+ b, err := base64.RawURLEncoding.DecodeString(headerVal)
+ if err != nil {
+ return nil, err
+ }
+ if len(b)%6 != 0 {
+ return nil, err
+ }
+ settings := make([]http2.Setting, 0)
+ for i := 0; i < len(b)/6; i++ {
+ settings = append(settings, http2.Setting{
+ ID: http2.SettingID(binary.BigEndian.Uint16(b[i*6 : i*6+2])),
+ Val: binary.BigEndian.Uint32(b[i*6+2 : i*6+6]),
+ })
+ }
+
+ return settings, nil
+}
+
+// getH2HeaderBytes return the headers in r a []bytes encoded by HPACK.
+func getH2HeaderBytes(r *http.Request, maxHeaderTableSize uint32) ([]byte, error) {
+ headerBytes := bytes.NewBuffer(nil)
+ hpackEnc := hpack.NewEncoder(headerBytes)
+ hpackEnc.SetMaxDynamicTableSize(maxHeaderTableSize)
+
+ // Section 8.1.2.3
+ err := hpackEnc.WriteField(hpack.HeaderField{
+ Name: ":method",
+ Value: r.Method,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ err = hpackEnc.WriteField(hpack.HeaderField{
+ Name: ":scheme",
+ Value: "http",
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ err = hpackEnc.WriteField(hpack.HeaderField{
+ Name: ":authority",
+ Value: r.Host,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ path := r.URL.Path
+ if r.URL.RawQuery != "" {
+ path = strings.Join([]string{path, r.URL.RawQuery}, "?")
+ }
+ err = hpackEnc.WriteField(hpack.HeaderField{
+ Name: ":path",
+ Value: path,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO Implement Section 8.3
+
+ for header, values := range r.Header {
+ // Skip non h2 headers
+ if isNonH2Header(header) {
+ continue
+ }
+ for _, v := range values {
+ err := hpackEnc.WriteField(hpack.HeaderField{
+ Name: strings.ToLower(header),
+ Value: v,
+ })
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ return headerBytes.Bytes(), nil
+}
+
+// Connection specific headers listed in RFC 7540 Section 8.1.2.2 that are not
+// suppose to be transferred to HTTP/2. The Http2-Settings header is skipped
+// since already use to create the HTTP/2 SETTINGS frame.
+var nonH2Headers = []string{
+ "Connection",
+ "Keep-Alive",
+ "Proxy-Connection",
+ "Transfer-Encoding",
+ "Upgrade",
+ "Http2-Settings",
+}
+
+// isNonH2Header returns true if header should not be transferred to HTTP/2.
+func isNonH2Header(header string) bool {
+ for _, nonH2h := range nonH2Headers {
+ if header == nonH2h {
+ return true
+ }
+ }
+ return false
+}
diff --git a/integration/fixtures/grpc/config_h2c.toml b/integration/fixtures/grpc/config_h2c.toml
index f36006016..7525185dc 100644
--- a/integration/fixtures/grpc/config_h2c.toml
+++ b/integration/fixtures/grpc/config_h2c.toml
@@ -1,15 +1,10 @@
-defaultEntryPoints = ["https"]
+defaultEntryPoints = ["http"]
-rootCAs = [ """{{ .CertContent }}""" ]
+debug=true
[entryPoints]
- [entryPoints.https]
- address = ":4443"
- [entryPoints.https.tls]
- [[entryPoints.https.tls.certificates]]
- certFile = """{{ .CertContent }}"""
- keyFile = """{{ .KeyContent }}"""
-
+ [entryPoints.http]
+ address = ":8081"
[api]
diff --git a/integration/fixtures/grpc/config_h2c_termination.toml b/integration/fixtures/grpc/config_h2c_termination.toml
new file mode 100644
index 000000000..41292acd3
--- /dev/null
+++ b/integration/fixtures/grpc/config_h2c_termination.toml
@@ -0,0 +1,25 @@
+defaultEntryPoints = ["https"]
+
+[entryPoints]
+ [entryPoints.https]
+ address = ":4443"
+ [entryPoints.https.tls]
+ [[entryPoints.https.tls.certificates]]
+ certFile = """{{ .CertContent }}"""
+ keyFile = """{{ .KeyContent }}"""
+[api]
+
+[file]
+
+[backends]
+ [backends.backend1]
+ [backends.backend1.servers.server1]
+ url = "h2c://127.0.0.1:{{ .GRPCServerPort }}"
+ weight = 1
+
+
+[frontends]
+ [frontends.frontend1]
+ backend = "backend1"
+ [frontends.frontend1.routes.test_1]
+ rule = "Host:127.0.0.1"
diff --git a/integration/grpc_test.go b/integration/grpc_test.go
index 311fd60ec..8ec9813a0 100644
--- a/integration/grpc_test.go
+++ b/integration/grpc_test.go
@@ -88,9 +88,27 @@ func getHelloClientGRPC() (helloworld.GreeterClient, func() error, error) {
}
-func callHelloClientGRPC(name string) (string, error) {
- client, closer, err := getHelloClientGRPC()
+func getHelloClientGRPCh2c() (helloworld.GreeterClient, func() error, error) {
+ conn, err := grpc.Dial("127.0.0.1:8081", grpc.WithInsecure())
+ if err != nil {
+ return nil, func() error { return nil }, err
+ }
+ return helloworld.NewGreeterClient(conn), conn.Close, nil
+
+}
+
+func callHelloClientGRPC(name string, secure bool) (string, error) {
+ var client helloworld.GreeterClient
+ var closer func() error
+ var err error
+
+ if secure {
+ client, closer, err = getHelloClientGRPC()
+ } else {
+ client, closer, err = getHelloClientGRPCh2c()
+ }
defer closer()
+
if err != nil {
return "", err
}
@@ -149,7 +167,7 @@ func (s *GRPCSuite) TestGRPC(c *check.C) {
var response string
err = try.Do(1*time.Second, func() error {
- response, err = callHelloClientGRPC("World")
+ response, err = callHelloClientGRPC("World", true)
return err
})
c.Assert(err, check.IsNil)
@@ -168,6 +186,44 @@ func (s *GRPCSuite) TestGRPCh2c(c *check.C) {
}()
file := s.adaptFile(c, "fixtures/grpc/config_h2c.toml", struct {
+ GRPCServerPort string
+ }{
+ GRPCServerPort: port,
+ })
+
+ defer os.Remove(file)
+ cmd, display := s.traefikCmd(withConfigFile(file))
+ defer display(c)
+
+ err = cmd.Start()
+ c.Assert(err, check.IsNil)
+ defer cmd.Process.Kill()
+
+ // wait for Traefik
+ err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("Host:127.0.0.1"))
+ c.Assert(err, check.IsNil)
+
+ var response string
+ err = try.Do(1*time.Second, func() error {
+ response, err = callHelloClientGRPC("World", false)
+ return err
+ })
+ c.Assert(err, check.IsNil)
+ c.Assert(response, check.Equals, "Hello World")
+}
+
+func (s *GRPCSuite) TestGRPCh2cTermination(c *check.C) {
+ lis, err := net.Listen("tcp", ":0")
+ _, port, err := net.SplitHostPort(lis.Addr().String())
+ c.Assert(err, check.IsNil)
+
+ go func() {
+ err := starth2cGRPCServer(lis, &myserver{})
+ c.Log(err)
+ c.Assert(err, check.IsNil)
+ }()
+
+ file := s.adaptFile(c, "fixtures/grpc/config_h2c_termination.toml", struct {
CertContent string
KeyContent string
GRPCServerPort string
@@ -191,7 +247,7 @@ func (s *GRPCSuite) TestGRPCh2c(c *check.C) {
var response string
err = try.Do(1*time.Second, func() error {
- response, err = callHelloClientGRPC("World")
+ response, err = callHelloClientGRPC("World", true)
return err
})
c.Assert(err, check.IsNil)
@@ -233,7 +289,7 @@ func (s *GRPCSuite) TestGRPCInsecure(c *check.C) {
var response string
err = try.Do(1*time.Second, func() error {
- response, err = callHelloClientGRPC("World")
+ response, err = callHelloClientGRPC("World", true)
return err
})
c.Assert(err, check.IsNil)
diff --git a/server/server.go b/server/server.go
index 7e3ffdcb8..9bf780d14 100644
--- a/server/server.go
+++ b/server/server.go
@@ -25,6 +25,7 @@ import (
"github.com/containous/traefik/cluster"
"github.com/containous/traefik/configuration"
"github.com/containous/traefik/configuration/router"
+ "github.com/containous/traefik/h2c"
"github.com/containous/traefik/healthcheck"
"github.com/containous/traefik/log"
"github.com/containous/traefik/metrics"
@@ -89,7 +90,7 @@ type EntryPoint struct {
type serverEntryPoints map[string]*serverEntryPoint
type serverEntryPoint struct {
- httpServer *http.Server
+ httpServer *h2c.Server
listener net.Listener
httpRouter *middlewares.HandlerSwitcher
certs *safe.Safe
@@ -747,7 +748,7 @@ func (s *Server) startServer(serverEntryPoint *serverEntryPoint) {
}
}
-func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.EntryPoint, router *middlewares.HandlerSwitcher, middlewares []negroni.Handler) (*http.Server, net.Listener, error) {
+func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.EntryPoint, router *middlewares.HandlerSwitcher, middlewares []negroni.Handler) (*h2c.Server, net.Listener, error) {
readTimeout, writeTimeout, idleTimeout := buildServerTimeouts(s.globalConfiguration)
log.Infof("Preparing server %s %+v with readTimeout=%s writeTimeout=%s idleTimeout=%s", entryPointName, entryPoint, readTimeout, writeTimeout, idleTimeout)
@@ -792,14 +793,16 @@ func (s *Server) prepareServer(entryPointName string, entryPoint *configuration.
}
}
- return &http.Server{
- Addr: entryPoint.Address,
- Handler: internalMuxRouter,
- TLSConfig: tlsConfig,
- ReadTimeout: readTimeout,
- WriteTimeout: writeTimeout,
- IdleTimeout: idleTimeout,
- ErrorLog: httpServerLogger,
+ return &h2c.Server{
+ Server: &http.Server{
+ Addr: entryPoint.Address,
+ Handler: internalMuxRouter,
+ TLSConfig: tlsConfig,
+ ReadTimeout: readTimeout,
+ WriteTimeout: writeTimeout,
+ IdleTimeout: idleTimeout,
+ ErrorLog: httpServerLogger,
+ },
},
listener,
nil