Caddy源码阅读

之前我们用了caddy的forwardproxy插件,来看看他是怎么做的

go内置的http服务器实现

http.ListenAndServe(":8080", nil)

如上即可启动go内置的http服务器,第二个参数是nil,于是go会使用内置的handler,代码如下

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil { // 如果为nil,则使用内置的ServeMux
		handler = DefaultServeMux
	}
	if req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}
	handler.ServeHTTP(rw, req)
}

DefaultServeMux 负责匹配url和handleFunc,确定改请求由谁处理。

caddy自定义的handle和其责任链模式

caddy自定义了handle,在

github.com/caddyserver/caddy/caddyhttp/HttpServer.Server
// ServeHTTP is the entry point of all HTTP requests.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer func() {
		// We absolutely need to be sure we stay alive up here,
		// even though, in theory, the errors middleware does this.
		if rec := recover(); rec != nil {
			log.Printf("[PANIC] %v", rec)
			DefaultErrorFunc(w, r, http.StatusInternalServerError)
		}
	}()

	// record the User-Agent string (with a cap on its length to mitigate attacks)
	ua := r.Header.Get("User-Agent")
	if len(ua) > 512 {
		ua = ua[:512]
	}
	uaHash := telemetry.FastHash([]byte(ua)) // this is a normalized field
	go telemetry.SetNested("http_user_agent", uaHash, ua)
	go telemetry.AppendUnique("http_user_agent_count", uaHash)
	go telemetry.Increment("http_request_count")

	// copy the original, unchanged URL into the context
	// so it can be referenced by middlewares
	urlCopy := *r.URL
	if r.URL.User != nil {
		userInfo := new(url.Userinfo)
		*userInfo = *r.URL.User
		urlCopy.User = userInfo
	}
	c := context.WithValue(r.Context(), OriginalURLCtxKey, urlCopy)
	r = r.WithContext(c)

	// Setup a replacer for the request that keeps track of placeholder
	// values across plugins.
	replacer := NewReplacer(r, nil, "")
	c = context.WithValue(r.Context(), ReplacerCtxKey, replacer)
	r = r.WithContext(c)

	w.Header().Set("Server", caddy.AppName)

	status, _ := s.serveHTTP(w, r)

	// Fallback error response in case error handling wasn't chained in
	if status >= 400 {
		DefaultErrorFunc(w, r, status)
	}
}

func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	// strip out the port because it's not used in virtual
	// hosting; the port is irrelevant because each listener
	// is on a different port.
	hostname, _, err := net.SplitHostPort(r.Host)
	if err != nil {
		hostname = r.Host
	}

	// look up the virtualhost; if no match, serve error
	vhost, pathPrefix := s.vhosts.Match(hostname + r.URL.Path)
	c := context.WithValue(r.Context(), caddy.CtxKey("path_prefix"), pathPrefix)
	r = r.WithContext(c)

	if vhost == nil {
		// check for ACME challenge even if vhost is nil;
		// could be a new host coming online soon - choose any
		// vhost's cert manager configuration, I guess
		if len(s.sites) > 0 && s.sites[0].TLS.Manager.HandleHTTPChallenge(w, r) {
			return 0, nil
		}

		// otherwise, log the error and write a message to the client
		remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
		if err != nil {
			remoteHost = r.RemoteAddr
		}
		WriteSiteNotFound(w, r) // don't add headers outside of this function (http.forwardproxy)
		log.Printf("[INFO] %s - No such site at %s (Remote: %s, Referer: %s)",
			hostname, s.Server.Addr, remoteHost, r.Header.Get("Referer"))
		return 0, nil
	}

	// we still check for ACME challenge if the vhost exists,
	// because the HTTP challenge might be disabled by its config
	if vhost.TLS.Manager.HandleHTTPChallenge(w, r) {
		return 0, nil
	}

	// trim the path portion of the site address from the beginning of
	// the URL path, so a request to example.com/foo/blog on the site
	// defined as example.com/foo appears as /blog instead of /foo/blog.
	if pathPrefix != "/" {
		r.URL = trimPathPrefix(r.URL, pathPrefix)
	}

	// enforce strict host matching, which ensures that the SNI
	// value (if any), matches the Host header; essential for
	// sites that rely on TLS ClientAuth sharing a port with
	// sites that do not - if mismatched, close the connection
	if vhost.StrictHostMatching && r.TLS != nil &&
		strings.ToLower(r.TLS.ServerName) != strings.ToLower(hostname) {
		r.Close = true
		log.Printf("[ERROR] %s - strict host matching: SNI (%s) and HTTP Host (%s) values differ",
			vhost.Addr, r.TLS.ServerName, hostname)
		return http.StatusForbidden, nil
	}

	return vhost.middlewareChain.ServeHTTP(w, r)
}