package main import ( "fmt" "github.com/yookoala/gofast" "log" "net" "net/http" "os" "path/filepath" "regexp" "strings" ) // FastCGI is a fastcgi client connection type FastCGI struct { Network string Addr string DocRoot string URLPrefix string //client gofast.Client } // NewFastCGI creates a new FastCGI struct func NewFastCGI(network, addr, docroot, urlPrefix string) (*FastCGI, error) { u := strings.TrimRight(urlPrefix, "/") return &FastCGI{network, addr, docroot, u}, nil } var fcgiPathInfo = regexp.MustCompile(`^(.*?\.php)(.*)$`) // ServeHTTP implements http.Handler interface func (f *FastCGI) ServeHTTP(w http.ResponseWriter, r *http.Request) { f.FastCGIPass(w, r) } // FastCGIPass pass the request to fastcgi socket func (f *FastCGI) FastCGIPass(w http.ResponseWriter, r *http.Request) { // make sure server not access the file out of document root p1 := filepath.Clean(filepath.Join(f.DocRoot, r.URL.Path)) p2 := filepath.Clean(f.DocRoot) if !strings.HasPrefix(p1, p2) { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "invalid url") return } var scriptName, pathInfo, scriptFileName string conn, err := net.Dial(f.Network, f.Addr) if err != nil { log.Println(err) w.WriteHeader(http.StatusBadGateway) return } defer conn.Close() client := gofast.NewClient("", conn, 20) urlPath := r.URL.Path if f.URLPrefix != "" { urlPath = strings.Replace(r.URL.Path, f.URLPrefix, "", 1) } p := fcgiPathInfo.FindStringSubmatch(urlPath) if len(p) < 2 { if strings.HasSuffix(r.URL.Path, "/") { // redirect to index.php scriptName = filepath.Join(r.URL.Path, "index.php") pathInfo = "" scriptFileName = filepath.Join(f.DocRoot, urlPath, "index.php") } else { // serve static file in php directory fn := filepath.Join(f.DocRoot, urlPath) http.ServeFile(w, r, fn) return } } else { scriptName = p[1] pathInfo = p[2] scriptFileName = filepath.Join(f.DocRoot, scriptName) } req := client.NewRequest(r) req.Params["DOCUMENT_URI"] = scriptName req.Params["SCRIPT_NAME"] = scriptName req.Params["PHP_SELF"] = scriptName req.Params["DOCUMENT_ROOT"] = f.DocRoot req.Params["PATH_INFO"] = pathInfo req.Params["SCRIPT_FILENAME"] = scriptFileName https := "off" scheme := "http" if r.TLS != nil { https = "on" scheme = "https" } req.Params["REQUEST_SCHEME"] = scheme req.Params["HTTPS"] = https host, port, _ := net.SplitHostPort(r.RemoteAddr) req.Params["REMOTE_ADDR"] = host req.Params["REMOTE_PORT"] = port host, port, err = net.SplitHostPort(r.Host) if err != nil { host = r.Host if scheme == "http" { port = "80" } else { port = "443" } } req.Params["SERVER_NAME"] = host req.Params["SERVER_PORT"] = port req.Params["SERVER_PROTOCOL"] = r.Proto for k, v := range r.Header { k = "HTTP_" + strings.ToUpper(strings.Replace(k, "-", "_", -1)) if _, ok := req.Params[k]; ok == false { req.Params[k] = strings.Join(v, ";") } } resp, err := client.Do(req) if err != nil { log.Println(err) w.WriteHeader(http.StatusBadGateway) return } err = resp.WriteTo(w, os.Stderr) if err != nil { log.Println(err) } resp.Close() }