我们都知道一些web脚本的执行是靠CGI去执行的,比如golang的net/http包下面cgi部分代码:
/*********省略*************/ cmd := &exec.Cmd{ Path: path, Args: append([]string{h.Path}, h.Args...), Dir: cwd, Env: env, Stderr: os.Stderr, // for now } if req.ContentLength != 0 { cmd.Stdin = req.Body } stdoutRead, err := cmd.StdoutPipe() //标准输出管道,比如php里面的echo "xxx"最终输出到这上面来了 //最终cgi会把stdout输出到http.ResponseWriter上面去 if err != nil { internalError(err) return } err = cmd.Start() //cmd的执行,比如解析完URL后执行php xxx.php if err != nil { internalError(err) return } if hook := testHookStartProcess; hook != nil { hook(cmd.Process) } defer cmd.Wait() defer stdoutRead.Close() /***********省略***************/
然后再分析一下golang的http服务器caddy的fastcgi是如何实现的:
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range h.Rules { // 匹配根路径 if !middleware.Path(r.URL.Path).Matches(rule.Path) { continue } // In addition to matching the path, a request must meet some // other criteria before being proxied as FastCGI. For example, // we probably want to exclude static assets (CSS, JS, images...) // but we also want to be flexible for the script we proxy to. fpath := r.URL.Path if idx, ok := middleware.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok { fpath = idx // Index file present. // If request path cannot be split, return error. if !h.canSplit(fpath, rule) { return http.StatusInternalServerError, ErrIndexMissingSplit } } else { // No index file present. // If request path cannot be split, ignore request. if !h.canSplit(fpath, rule) { continue } } // These criteria work well in this order for PHP sites if !h.exists(fpath) || fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) { // Create environment for CGI script env, err := h.buildEnv(r, rule, fpath) if err != nil { return http.StatusInternalServerError, err } // Connect to FastCGI gateway //做一次TCP连接到rule的address上 fcgi, err := getClient(&rule) if err != nil { return http.StatusBadGateway, err } var resp *http.Response contentLength, _ := strconv.Atoi(r.Header.Get("Content-Length")) switch r.Method { case "HEAD": resp, err = fcgi.Head(env) case "GET": resp, err = fcgi.Get(env) case "OPTIONS": resp, err = fcgi.Options(env) case "POST": resp, err = fcgi.Post(env, r.Header.Get("Content-Type"), r.Body, contentLength) case "PUT": resp, err = fcgi.Put(env, r.Header.Get("Content-Type"), r.Body, contentLength) case "PATCH": resp, err = fcgi.Patch(env, r.Header.Get("Content-Type"), r.Body, contentLength) case "DELETE": resp, err = fcgi.Delete(env, r.Header.Get("Content-Type"), r.Body, contentLength) default: return http.StatusMethodNotAllowed, nil } if resp.Body != nil { defer resp.Body.Close() } if err != nil && err != io.EOF { return http.StatusBadGateway, err } writeHeader(w, resp) //主要的功能是写http的header // Write the response body // TODO: If this has an error, the response will already be // partly written. We should copy out of resp.Body into a buffer // first, then write it to the response... _, err = io.Copy(w, resp.Body) //主要的功能是写http的body if err != nil { return http.StatusBadGateway, err } return 0, nil } } return h.Next.ServeHTTP(w, r) }
可见,再解析完http的request后,golang的http的钩子handler里面,fastcgi主要是做连接,写http的header和body,然后具体的执行依赖cgi具体去执行脚本