【微服务网关——https与http2代理实现】

07-08 1335阅读

1.https与http2代理

1.1 重新认识https与http2

  • https是http安全版本
  • http2是一种传输协议
  • 两者并没有本质联系

    1.1.1 https与http的区别

    HTTP(超文本传输协议)和 HTTPS(安全超文本传输协议)是用于在网络上交换数据的两种协议。HTTPS 是 HTTP 的扩展,它在 HTTP 的基础上增加了加密层SSL/TLS ,以保护数据的安全。

    • HTTPS
      • 加密: HTTPS 使用 SSL/TLS 协议来加密传输的数据,保护数据免受窃听。
      • 验证身份: HTTPS 通过数字证书验证服务器的身份,确保数据被发送到正确的服务器。
      • 数据完整性: HTTPS 通过加密和校验机制,确保传输的数据不会被篡改。
      • 端口:443
        https请求流程

        【微服务网关——https与http2代理实现】

        1.1.2 http1.1与http2区别

        • HTTP/1.1
          • 文本协议:数据以明文格式传输,HTTP/1.1 请求和响应是人类可读的文本。
          • 单一请求-响应通道:每次请求和响应通过单独的 TCP 连接传输,连接复用有限。
          • 队头阻塞:由于请求是按顺序处理的,前一个请求处理未完成时,后续请求会被阻塞。
          • 连接管理:需要为每个请求创建一个新的连接,或者使用连接保持(keep-alive)来重用连接。
          • HTTP/2
            • 二进制协议:数据以二进制格式传输,增加了解析和处理的效率。
            • 多路复用:一个 TCP 连接可以同时承载多个请求和响应,消除了队头阻塞(Head-of-Line Blocking)的问题。
            • 头部压缩:使用 HPACK 算法对 HTTP 头部进行压缩,减少了冗余数据的传输。
            • 优先级控制:可以设置请求的优先级,优化资源的分配和加载顺序。

              1.2 http2与https的关系

              • http2代表多路复用的传输协议
              • https代表http服务器使用了加密协议
              • 一个启用https的服务器不一定使用http2
              • 但是使用http2的服务器必须启用https(浏览器强制)

                1.3 http2设计目标

                • 感知延迟有实质上改进
                • 解决HTTP1.1中的“队首阻塞”问题
                • 并行操作无需与服务器建立多个连接
                • 保持HTTP1.1语义,只是标准拓展并非替代

                  【微服务网关——https与http2代理实现】

                  2.https与http2代理实现

                  2.1 创建下游测试服务器

                  2.1.1 代码实现

                  package main
                  import (
                  	"fmt"
                  	"github.com/e421083458/gateway_demo/demo/proxy/reverse_proxy_https/testdata"
                  	"golang.org/x/net/http2"
                  	"io"
                  	"log"
                  	"net/http"
                  	"os"
                  	"os/signal"
                  	"syscall"
                  	"time"
                  )
                  func main() {
                  	rs1 := &RealServer{Addr: "127.0.0.1:3003"}
                  	rs1.Run()
                  	rs2 := &RealServer{Addr: "127.0.0.1:3004"}
                  	rs2.Run()
                  	//监听关闭信号
                  	quit := make(chan os.Signal)
                  	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
                  	
                  	Addr string
                  }
                  func (r *RealServer) Run() {
                  	log.Println("Starting httpserver at " + r.Addr)
                  	mux := http.NewServeMux()
                  	mux.HandleFunc("/", r.HelloHandler)
                  	mux.HandleFunc("/base/error", r.ErrorHandler)
                  	server := &http.Server{
                  		Addr:         r.Addr,
                  		WriteTimeout: time.Second * 3,
                  		Handler:      mux,
                  	}
                  	go func() {
                  		// 开启http2
                  		http2.ConfigureServer(server, &http2.Server{})
                  		// 开启https
                  		log.Fatal(server.ListenAndServeTLS(testdata.Path("server.crt"), testdata.Path("server.key")))
                  	}()
                  }
                  func (r *RealServer) HelloHandler(w http.ResponseWriter, req *http.Request) {
                  	upath := fmt.Sprintf("http://%s%s\n", r.Addr, req.URL.Path)
                  	io.WriteString(w, upath)
                  }
                  func (r *RealServer) ErrorHandler(w http.ResponseWriter, req *http.Request) {
                  	upath := "error handler"
                  	w.WriteHeader(500)
                  	io.WriteString(w, upath)
                  }
                  
                      if s == nil {
                          panic("nil *http.Server") // 如果传入的 *http.Server 是 nil,程序会崩溃
                      }
                      if conf == nil {
                          conf = new(Server) // 如果传入的 Server 配置是 nil,则新建一个默认的 Server 配置
                      }
                      // 初始化 conf 的内部状态
                      conf.state = &serverInternalState{activeConns: make(map[*serverConn]struct{})}
                      
                      // 如果 conf 中的 IdleTimeout 没有设置,则使用 s 的 IdleTimeout 或 ReadTimeout
                      if h1, h2 := s, conf; h2.IdleTimeout == 0 {
                          if h1.IdleTimeout != 0 {
                              h2.IdleTimeout = h1.IdleTimeout
                          } else {
                              h2.IdleTimeout = h1.ReadTimeout
                          }
                      }
                      // 注册优雅关机的回调函数
                      s.RegisterOnShutdown(conf.state.startGracefulShutdown)
                      // 如果没有 TLS 配置,创建一个默认的 TLS 配置
                      if s.TLSConfig == nil {
                          s.TLSConfig = new(tls.Config)
                      } else if s.TLSConfig.CipherSuites != nil {
                          // 如果已提供了 CipherSuite 列表,检查它的顺序或是否缺少必要的 CipherSuite
                          haveRequired := false
                          sawBad := false
                          for i, cs := range s.TLSConfig.CipherSuites {
                              switch cs {
                              case tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
                                  // 另一种 MTI 密码套件,防止只支持 ECDSA 的服务器。
                                  // 详情请参见 http://golang.org/cl/30721。
                                  tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
                                  haveRequired = true
                              }
                              if isBadCipher(cs) {
                                  sawBad = true
                              } else if sawBad {
                                  return fmt.Errorf("http2: TLSConfig.CipherSuites 索引 %d 包含 HTTP/2 批准的密码套件 (%#04x),但它在未批准的密码套件之后。这样配置,可能会导致不支持先前批准的密码套件的客户端被分配一个未批准的套件并拒绝连接。", i, cs)
                              }
                          }
                          if !haveRequired {
                              return fmt.Errorf("http2: TLSConfig.CipherSuites 缺少 HTTP/2 所需的 AES_128_GCM_SHA256 密码(至少需要 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 或 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 之一)。")
                          }
                      }
                      // 注意:这里没有设置 MinVersion 为 tls.VersionTLS12,
                      // 因为我们不想干扰用户服务器上的 HTTP/1.1 流量。我们稍后在接受连接时强制执行 TLS 1.2。
                      // 理想情况下,这应该在下一步协议选择中完成,但使用 TLS 
                          if p == NextProtoTLS {
                              haveNPN = true
                              break
                          }
                      }
                      if !haveNPN {
                          s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, NextProtoTLS) // 添加 HTTP/2 协议
                      }
                      if s.TLSNextProto == nil {
                          s.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){} // 初始化 TLSNextProto 映射
                      }
                      // 定义 protoHandler,用于处理新连接
                      protoHandler := func(hs *http.Server, c *tls.Conn, h http.Handler) {
                          if testHookOnConn != nil {
                              testHookOnConn() // 测试钩子,如果存在,则调用
                          }
                          // TLSNextProto 接口早于 context 出现,因此 net/http 包通过 Handler 上一个未公开的方法传递每个连接的基础 context。
                          // 这仅供内部 net/http
                              BaseContext() context.Context
                          }
                          if bc, ok := h.(baseContexter); ok {
                              ctx = bc.BaseContext() // 获取 base context
                          }
                          // 服务连接
                          conf.ServeConn(c, &ServeConnOpts{
                              Context:    ctx,
                              Handler:    h,
                              BaseConfig: hs,
                          })
                      }
                      s.TLSNextProto[NextProtoTLS] = protoHandler // 设置 protoHandler
                      return nil // 成功返回 nil
                  }
                  
                  	DialContext: (&net.Dialer{
                  		Timeout:   30 * time.Second, //连接超时
                  		KeepAlive: 30 * time.Second, //长连接超时时间
                  	}).DialContext,
                  	//TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
                  	TLSClientConfig: func() *tls.Config {
                  		pool := x509.NewCertPool()
                  		caCertPath := testdata.Path("ca.crt")
                  		caCrt, _ := ioutil.ReadFile(caCertPath)
                  		pool.AppendCertsFromPEM(caCrt)
                  		return &tls.Config{RootCAs: pool}
                  	}(),
                  	MaxIdleConns:          100,              //最大空闲连接
                  	IdleConnTimeout:       90 * time.Second, //空闲超时时间
                  	TLSHandshakeTimeout:   10 * time.Second, //tls握手超时时间
                  	ExpectContinueTimeout: 1 * time.Second,  //100-continue 超时时间
                  }
                  func NewMultipleHostsReverseProxy(targets []*url.URL) *httputil.ReverseProxy {
                  	//请求协调者
                  	director := func(req *http.Request) {
                  		targetIndex := rand.Intn(len(targets))
                  		target := targets[targetIndex]
                  		targetQuery := target.RawQuery
                  		fmt.Println("target.Scheme")
                  		fmt.Println(target.Scheme)
                  		req.URL.Scheme = target.Scheme
                  		req.URL.Host = target.Host
                  		req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
                  		if targetQuery == "" || req.URL.RawQuery == "" {
                  			req.URL.RawQuery = targetQuery + req.URL.RawQuery
                  		} else {
                  			req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
                  		}
                  		if _, ok := req.Header["User-Agent"]; !ok {
                  			req.Header.Set("User-Agent", "user-agent")
                  		}
                  	}
                  	http2.ConfigureTransport(transport)
                  	return &httputil.ReverseProxy{Director: director, Transport: transport,}
                  }
                  func singleJoiningSlash(a, b string) string {
                  	aslash := strings.HasSuffix(a, "/")
                  	bslash := strings.HasPrefix(b, "/")
                  	switch {
                  	case aslash && bslash:
                  		return a + b[1:]
                  	case !aslash && !bslash:
                  		return a + "/" + b
                  	}
                  	return a + b
                  }
                  
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]