From 5e9515ff091a8110a41548f823fd6e0f7a1c1f48 Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Wed, 4 Nov 2020 12:48:55 -0800 Subject: [PATCH] Support FastCGI --- CONFIGURATION.md | 20 +++++++- README.md | 12 +++-- config.go | 36 +++++++++++++-- go.mod | 3 +- go.sum | 42 +++++++++++++++-- server.go | 118 ++++++++++++++++++++++++++++++++++------------- server_fcgi.go | 57 +++++++++++++++++++++++ 7 files changed, 240 insertions(+), 48 deletions(-) create mode 100644 server_fcgi.go diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 43c784b..d4e3dbe 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -74,7 +74,7 @@ Serve static files from specified root directory. ##### Proxy -Forward request to Gemini server at specified URL. +Forward requests to Gemini server at specified URL. Use the pseudo-scheme `gemini-insecure://` to disable certificate verification. @@ -102,6 +102,18 @@ Request text input from user. Request sensitive text input from the user. Text will not be shown as it is entered. +##### Type + +Content type is normally detected automatically, defaulting to +`text/gemini; charset=utf-8`. This option forces a specific content type. + +##### FastCGI + +Forward requests to [FastCGI](https://en.wikipedia.org/wiki/FastCGI) server at +specified address or path. + +A `Root` attribute must also be specified to use `FastCGI`. + # Example config.yaml ```yaml @@ -118,9 +130,13 @@ hosts: cert: /srv/gemini.rocks/data/cert.crt key: /srv/gemini.rocks/data/cert.key paths: + - + path: ^/sites/.*\.php$ + root: /home/geminirocks/data + fastcgi: unix:///var/run/php.sock - path: /sites - root: /home/geminirocks/data/sites + root: /home/geminirocks/data listdirectory: true - path: ^/(help|info)$ diff --git a/README.md b/README.md index 0bcf700..d95399e 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,10 @@ This page is also available at [gemini://twins.rocketnine.space](gemini://twins. - Serve static files - Directory listing (when enabled) -- Serve the output of system commands - Reverse proxy requests + - TCP + - [FastCGI](https://en.wikipedia.org/wiki/FastCGI) +- Serve system command output - Reload configuration on `SIGHUP` ## Download @@ -39,7 +41,7 @@ Please share issues and suggestions [here](https://gitlab.com/tslocum/twins/issu ## Dependencies -- [go-gemini](https://github.com/makeworld-the-better-one/go-gemini) -- [go-shellquote](https://github.com/kballard/go-shellquote) -- [filetype](https://github.com/h2non/filetype) -- [yaml](https://github.com/go-yaml/yaml/tree/v3) +- [filetype](https://github.com/h2non/filetype) - MIME type detection +- [gofast](https://github.com/yookoala/gofast) - FastCGI client +- [go-shellquote](https://github.com/kballard/go-shellquote) - Shell string quoting +- [yaml](https://github.com/go-yaml/yaml/tree/v3) - Configuration parsing diff --git a/config.go b/config.go index a31600c..2efa4ce 100644 --- a/config.go +++ b/config.go @@ -5,12 +5,15 @@ import ( "errors" "io/ioutil" "log" + "net/url" "os" "regexp" "strconv" "strings" + "time" "github.com/kballard/go-shellquote" + "github.com/yookoala/gofast" "gopkg.in/yaml.v3" ) @@ -32,6 +35,12 @@ type pathConfig struct { // List directory entries ListDirectory bool + // Content type + Type string + + // FastCGI server address + FastCGI string + r *regexp.Regexp cmd []string } @@ -49,8 +58,9 @@ type serverConfig struct { Hosts map[string]*hostConfig - hostname string - port int + hostname string + port int + fcgiPools map[string]*gofast.ClientPool } var config *serverConfig @@ -92,7 +102,8 @@ func readconfig(configPath string) error { } } - for _, host := range config.Hosts { + config.fcgiPools = make(map[string]*gofast.ClientPool) + for hostname, host := range config.Hosts { if host.Cert == "" || host.Key == "" { log.Fatal("a certificate must be specified for each domain (gemini requires TLS for all connections)") } @@ -109,7 +120,7 @@ func readconfig(configPath string) error { } else if (serve.Root != "" && (serve.Proxy != "" || serve.Command != "")) || (serve.Proxy != "" && (serve.Root != "" || serve.Command != "")) || (serve.Command != "" && (serve.Root != "" || serve.Proxy != "")) { - log.Fatal("only one root, reverse proxy or command may specified in a serve entry") + log.Fatal("only one root, proxy or command resource may specified for a path") } if serve.Path[0] == '^' { @@ -118,7 +129,22 @@ func readconfig(configPath string) error { serve.Path = serve.Path[:len(serve.Path)-1] } - if serve.Command != "" { + if serve.FastCGI != "" { + if serve.Root == "" { + log.Fatalf("root must be specified to use fastcgi resource %s of path %s%s", serve.FastCGI, hostname, serve.Path) + } + + if config.fcgiPools[serve.FastCGI] == nil { + f, err := url.Parse(serve.FastCGI) + if err != nil { + log.Fatalf("failed to parse fastcgi resource %s: %s", serve.FastCGI, err) + } + + connFactory := gofast.SimpleConnFactory(f.Scheme, f.Host+f.Path) + clientFactory := gofast.SimpleClientFactory(connFactory, 0) + config.fcgiPools[serve.FastCGI] = gofast.NewClientPool(clientFactory, 1, 1*time.Minute) + } + } else if serve.Command != "" { serve.cmd, err = shellquote.Split(serve.Command) if err != nil { log.Fatalf("failed to parse command %s: %s", serve.cmd, err) diff --git a/go.mod b/go.mod index bc719d4..d9b483d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.15 require ( github.com/h2non/filetype v1.1.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/makeworld-the-better-one/go-gemini v0.9.0 + github.com/yookoala/gofast v0.4.1-0.20201013050739-975113c54107 + golang.org/x/tools v0.0.0-20201104193857-22bd85271a8b // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 ) diff --git a/go.sum b/go.sum index 0087166..97afc8f 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,46 @@ -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/go-restit/lzjson v0.0.0-20161206095556-efe3c53acc68/go.mod h1:7vXSKQt83WmbPeyVjCfNT9YDJ5BUFmcwFsEjI9SCvYM= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/h2non/filetype v1.1.0 h1:Or/gjocJrJRNK/Cri/TDEKFjAR+cfG6eK65NGYB6gBA= github.com/h2non/filetype v1.1.0/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/makeworld-the-better-one/go-gemini v0.9.0 h1:Iz4ywRDrfsyoR8xZOkSKGXXftMR2spIV6ibVuhrKvSw= -github.com/makeworld-the-better-one/go-gemini v0.9.0/go.mod h1:P7/FbZ+IEIbA/d+A0Y3w2GNgD8SA2AcNv7aDGJbaWG4= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/yookoala/gofast v0.4.1-0.20201013050739-975113c54107 h1:wfqP/vw5tHVeFQJbnmyXSi7E2ZeshpKhR4kuUR5B7yQ= +github.com/yookoala/gofast v0.4.1-0.20201013050739-975113c54107/go.mod h1:OJU201Q6HCaE1cASckaTbMm3KB6e0cZxK0mgqfwOKvQ= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200908211811-12e1bf57a112/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201104193857-22bd85271a8b h1:ILx+nYAUTq4JtXxrXKQ82j7nruEwlXRfUn+kxtsnElg= +golang.org/x/tools v0.0.0-20201104193857-22bd85271a8b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.38.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server.go b/server.go index 4055b7f..fd8386b 100644 --- a/server.go +++ b/server.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "crypto/tls" + "crypto/x509" "fmt" "io" "log" @@ -20,10 +21,34 @@ import ( "unicode/utf8" "github.com/h2non/filetype" - "github.com/makeworld-the-better-one/go-gemini" ) -const readTimeout = 30 * time.Second +const ( + readTimeout = 30 * time.Second + + urlMaxLength = 1024 +) + +const ( + statusInput = 10 + statusSensitiveInput = 11 + + statusSuccess = 20 + + statusRedirectTemporary = 30 + statusRedirectPermanent = 31 + + statusTemporaryFailure = 40 + statusUnavailable = 41 + statusCGIError = 42 + statusProxyError = 43 + + statusPermanentFailure = 50 + statusNotFound = 51 + statusGone = 52 + statusProxyRequestRefused = 53 + statusBadRequest = 59 +) func writeHeader(c net.Conn, code int, meta string) { fmt.Fprintf(c, "%d %s\r\n", code, meta) @@ -36,15 +61,15 @@ func writeHeader(c net.Conn, code int, meta string) { func writeStatus(c net.Conn, code int) { var meta string switch code { - case gemini.StatusTemporaryFailure: + case statusTemporaryFailure: meta = "Temporary failure" - case gemini.StatusProxyError: + case statusProxyError: meta = "Proxy error" - case gemini.StatusBadRequest: + case statusBadRequest: meta = "Bad request" - case gemini.StatusNotFound: + case statusNotFound: meta = "Not found" - case gemini.StatusProxyRequestRefused: + case statusProxyRequestRefused: meta = "Proxy request refused" } writeHeader(c, code, meta) @@ -74,7 +99,7 @@ func serveDirectory(c net.Conn, request *url.URL, dirPath string) { return nil }) if err != nil { - writeStatus(c, gemini.StatusTemporaryFailure) + writeStatus(c, statusTemporaryFailure) return } // List directories first @@ -87,7 +112,7 @@ func serveDirectory(c net.Conn, request *url.URL, dirPath string) { return i < j }) - writeHeader(c, gemini.StatusSuccess, "text/gemini; charset=utf-8") + writeHeader(c, statusSuccess, "text/gemini; charset=utf-8") fmt.Fprintf(c, "# %s\r\n", request.Path) if numDirs > 0 || numFiles > 0 { @@ -141,7 +166,7 @@ func serveDirectory(c net.Conn, request *url.URL, dirPath string) { func serveFile(c net.Conn, request *url.URL, requestData, filePath string, listDir bool) { fi, err := os.Stat(filePath) if err != nil { - writeStatus(c, gemini.StatusNotFound) + writeStatus(c, statusNotFound) return } @@ -152,7 +177,7 @@ func serveFile(c net.Conn, request *url.URL, requestData, filePath string, listD if requestData[len(requestData)-1] != '/' { // Add trailing slash log.Println(requestData) - writeHeader(c, gemini.StatusRedirectPermanent, requestData+"/") + writeHeader(c, statusRedirectPermanent, requestData+"/") return } @@ -172,10 +197,10 @@ func serveFile(c net.Conn, request *url.URL, requestData, filePath string, listD serveDirectory(c, request, originalPath) return } - writeStatus(c, gemini.StatusNotFound) + writeStatus(c, statusNotFound) return } else if err != nil { - writeStatus(c, gemini.StatusTemporaryFailure) + writeStatus(c, statusTemporaryFailure) return } @@ -202,7 +227,7 @@ func serveFile(c net.Conn, request *url.URL, requestData, filePath string, listD if mimeType == "" { mimeType = "text/gemini; charset=utf-8" } - writeHeader(c, gemini.StatusSuccess, mimeType) + writeHeader(c, statusSuccess, mimeType) // Write body c.Write(buf[:n]) @@ -221,7 +246,7 @@ func serveProxy(c net.Conn, requestData, proxyURL string) { } proxy, err := tls.Dial("tcp", proxyURL, tlsConfig) if err != nil { - writeStatus(c, gemini.StatusProxyError) + writeStatus(c, statusProxyError) return } defer proxy.Close() @@ -254,11 +279,11 @@ func serveCommand(c net.Conn, userInput string, command []string) { err := cmd.Run() if err != nil { - writeStatus(c, gemini.StatusProxyError) + writeStatus(c, statusProxyError) return } - writeHeader(c, gemini.StatusSuccess, "text/gemini; charset=utf-8") + writeHeader(c, statusSuccess, "text/gemini; charset=utf-8") c.Write(buf.Bytes()) if verbose { @@ -293,7 +318,7 @@ func replaceWithUserInput(command []string, userInput string) []string { return newCommand } -func handleConn(c net.Conn) { +func handleConn(c *tls.Conn) { if verbose { t := time.Now() defer func() { @@ -318,28 +343,39 @@ func handleConn(c net.Conn) { } if err := scanner.Err(); err != nil { log.Println(scanner.Text(), "FAILED") - writeStatus(c, gemini.StatusBadRequest) + writeStatus(c, statusBadRequest) return } + state := c.ConnectionState() + certs := state.PeerCertificates + var clientCertKeys [][]byte + for _, cert := range certs { + pubKey, err := x509.MarshalPKIXPublicKey(cert.PublicKey) + if err != nil { + continue + } + clientCertKeys = append(clientCertKeys, pubKey) + } + if verbose { log.Printf("> %s\n", requestData) } - if len(requestData) > gemini.URLMaxLength || !utf8.ValidString(requestData) { - writeStatus(c, gemini.StatusBadRequest) + if len(requestData) > urlMaxLength || !utf8.ValidString(requestData) { + writeStatus(c, statusBadRequest) return } request, err := url.Parse(requestData) if err != nil { - writeStatus(c, gemini.StatusBadRequest) + writeStatus(c, statusBadRequest) return } requestHostname := request.Hostname() if requestHostname == "" || strings.ContainsRune(requestHostname, ' ') { - writeStatus(c, gemini.StatusBadRequest) + writeStatus(c, statusBadRequest) return } @@ -355,12 +391,12 @@ func handleConn(c net.Conn) { request.Scheme = "gemini" } if request.Scheme != "gemini" || (requestPort > 0 && requestPort != config.port) { - writeStatus(c, gemini.StatusProxyRequestRefused) + writeStatus(c, statusProxyRequestRefused) } if request.Path == "" { // Redirect to / - writeHeader(c, gemini.StatusRedirectPermanent, requestData+"/") + writeHeader(c, statusRedirectPermanent, requestData+"/") return } @@ -371,7 +407,7 @@ func handleConn(c net.Conn) { } requestQuery, err := url.QueryUnescape(request.RawQuery) if err != nil { - writeStatus(c, gemini.StatusBadRequest) + writeStatus(c, statusBadRequest) return } @@ -392,10 +428,10 @@ func handleConn(c net.Conn) { requireInput := serve.Input != "" || serve.SensitiveInput != "" if requestQuery == "" && requireInput { if serve.Input != "" { - writeHeader(c, gemini.StatusInput, serve.Input) + writeHeader(c, statusInput, serve.Input) return } else if serve.SensitiveInput != "" { - writeHeader(c, gemini.StatusSensitiveInput, serve.SensitiveInput) + writeHeader(c, statusSensitiveInput, serve.SensitiveInput) return } } @@ -404,6 +440,16 @@ func handleConn(c net.Conn) { if serve.Proxy != "" { serveProxy(c, requestData, serve.Proxy) return + } else if serve.FastCGI != "" { + contentType := "text/gemini; charset=utf-8" + if serve.Type != "" { + contentType = serve.Type + } + writeHeader(c, statusSuccess, contentType) + + filePath := path.Join(serve.Root, request.Path[1:]) + serveFastCGI(c, config.fcgiPools[serve.FastCGI], request, filePath) + return } else if serve.cmd != nil { if requireInput { newCommand := replaceWithUserInput(serve.cmd, requestQuery) @@ -421,6 +467,16 @@ func handleConn(c net.Conn) { if serve.Proxy != "" { serveProxy(c, requestData, serve.Proxy) return + } else if serve.FastCGI != "" { + contentType := "text/gemini; charset=utf-8" + if serve.Type != "" { + contentType = serve.Type + } + writeHeader(c, statusSuccess, contentType) + + filePath := path.Join(serve.Root, request.Path[1:]) + serveFastCGI(c, config.fcgiPools[serve.FastCGI], request, filePath) + return } else if serve.cmd != nil { if requireInput { newCommand := replaceWithUserInput(serve.cmd, requestQuery) @@ -444,9 +500,9 @@ func handleConn(c net.Conn) { } if matchedHost { - writeStatus(c, gemini.StatusNotFound) + writeStatus(c, statusNotFound) } else { - writeStatus(c, gemini.StatusProxyRequestRefused) + writeStatus(c, statusProxyRequestRefused) } } @@ -468,7 +524,7 @@ func handleListener(l net.Listener) { log.Fatal(err) } - go handleConn(conn) + go handleConn(conn.(*tls.Conn)) } } diff --git a/server_fcgi.go b/server_fcgi.go new file mode 100644 index 0000000..2e10b8d --- /dev/null +++ b/server_fcgi.go @@ -0,0 +1,57 @@ +package main + +import ( + "bytes" + "io" + "io/ioutil" + "log" + "net" + "net/http" + "net/url" + + "github.com/yookoala/gofast" +) + +type responseWriter struct { + io.WriteCloser + header http.Header +} + +func newResponseWriter(out io.WriteCloser) *responseWriter { + return &responseWriter{ + WriteCloser: out, + header: make(http.Header), + } +} + +func (w *responseWriter) Header() http.Header { + return w.header +} + +func (w *responseWriter) WriteHeader(statusCode int) { + // Do nothing +} + +func serveFastCGI(c net.Conn, clientPool *gofast.ClientPool, reqURL *url.URL, filePath string) { + r := &http.Request{ + Method: "GET", + URL: reqURL, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(http.Header), + Body: ioutil.NopCloser(bytes.NewReader(nil)), + Host: reqURL.Host, + } + + gofast. + NewHandler( + gofast.NewFileEndpoint(filePath)(gofast.BasicSession), + clientPool.CreateClient, + ). + ServeHTTP(newResponseWriter(c), r) + + if verbose { + log.Printf("< exec %s\n", filePath) + } +}