git » gofer » commit 577838d

http: Support CGI target scheme

author Alberto Bertogli
2020-05-04 10:14:55 UTC
committer Alberto Bertogli
2020-05-04 10:16:18 UTC
parent ceaa4729bbb5e0af8c1a8558ebc6378bf7aec9b9

http: Support CGI target scheme

This patch implements support for a target scheme that runs a CGI
program to handle the request.

proxy/http.go +46 -0
trace/trace.go +13 -0

diff --git a/proxy/http.go b/proxy/http.go
index d29419c..890db2a 100644
--- a/proxy/http.go
+++ b/proxy/http.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	golog "log"
 	"net/http"
+	"net/http/cgi"
 	"net/http/httputil"
 	"net/url"
 	"strings"
@@ -49,6 +50,8 @@ func httpServer(conf config.HTTP) *http.Server {
 			mux.Handle(from, makeStatic(from, *toURL))
 		case "redirect":
 			mux.Handle(from, makeRedirect(from, *toURL))
+		case "cgi":
+			mux.Handle(from, makeCGI(from, *toURL))
 		default:
 			log.Fatalf("route %q -> %q: invalid destination scheme %q",
 				from, to, toURL.Scheme)
@@ -198,6 +201,49 @@ func makeStatic(from string, to url.URL) http.Handler {
 	)
 }
 
+func makeCGI(from string, to url.URL) http.Handler {
+	from = stripDomain(from)
+	path := to.Path
+	if path == "" {
+		// This happens for relative paths, which are fine in this context.
+		path = to.Opaque
+	}
+	args := queryToArgs(to.RawQuery)
+
+	return WithLogging("http:cgi",
+		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			tr, _ := trace.FromContext(r.Context())
+			tr.Debugf("exec %q %q", path, args)
+			h := cgi.Handler{
+				Path:   path,
+				Args:   args,
+				Root:   from,
+				Logger: golog.New(tr, "", golog.Lshortfile),
+				Stderr: tr,
+			}
+			h.ServeHTTP(w, r)
+		}),
+	)
+}
+
+func queryToArgs(query string) []string {
+	args := []string{}
+	for query != "" {
+		comp := query
+		if i := strings.IndexAny(comp, "&;"); i >= 0 {
+			comp, query = comp[:i], comp[i+1:]
+		} else {
+			query = ""
+		}
+
+		comp, _ = url.QueryUnescape(comp)
+		args = append(args, comp)
+
+	}
+
+	return args
+}
+
 func makeRedirect(from string, to url.URL) http.Handler {
 	from = stripDomain(from)
 
diff --git a/trace/trace.go b/trace/trace.go
index 709dd64..3a59353 100644
--- a/trace/trace.go
+++ b/trace/trace.go
@@ -88,6 +88,19 @@ func (t *Trace) Finish() {
 	t.t.Finish()
 }
 
+// Write so Trace implements io.Writer, which means it can be used as output
+// for log.Logger.
+func (t *Trace) Write(p []byte) (n int, err error) {
+	lines := strings.Split(string(p), "\n")
+	for _, line := range lines {
+		if strings.TrimSpace(line) == "" {
+			continue
+		}
+		t.Printf("%s", line)
+	}
+	return len(p), nil
+}
+
 // EventLog is used for tracing long-lived objects.
 type EventLog struct {
 	family string