mirror of https://github.com/perkeep/perkeep.git
Go CGI HTTP handler, for gitweb.cgi
This commit is contained in:
parent
ebcc37560c
commit
8b13da6ec0
2
build.pl
2
build.pl
|
@ -174,7 +174,7 @@ __DATA__
|
|||
- lib/go/ext/openpgp/error
|
||||
- lib/go/ext/openpgp/armor
|
||||
./website/Makefile
|
||||
# (no deps, yet)
|
||||
- lib/go/line
|
||||
./clients/go/camput/Makefile
|
||||
- lib/go/client
|
||||
- lib/go/blobref
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
PREREQ=$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/camli/encoding/line.a
|
||||
|
||||
TARG=camweb
|
||||
GOFILES=\
|
||||
camweb.go\
|
||||
cgi.go\
|
||||
|
||||
include $(GOROOT)/src/Make.cmd
|
||||
|
||||
|
|
|
@ -161,6 +161,7 @@ func main() {
|
|||
mux := http.DefaultServeMux
|
||||
mux.Handle("/favicon.ico", http.FileServer(path.Join(*root, "static"), "/"))
|
||||
mux.Handle("/static/", http.FileServer(path.Join(*root, "static"), "/static/"))
|
||||
mux.Handle("/test.cgi", &CgiHandler{ExecutablePath: path.Join(*root, "test.cgi")})
|
||||
mux.HandleFunc("/", mainHandler)
|
||||
|
||||
if err := http.ListenAndServe(*httpAddr, mux); err != nil {
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
package main
|
||||
|
||||
// Package cgi implements a CGI (Common Gateway Interface) HTTP handler as specified
|
||||
// in RFC 3875. Using CGI isn't usually incredibly efficient and this package
|
||||
// is intended primarily for compatibility.
|
||||
|
||||
import (
|
||||
"camli/encoding/line"
|
||||
"exec"
|
||||
"fmt"
|
||||
"http"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CgiHandler struct {
|
||||
ExecutablePath string
|
||||
// TODO: custom log.Logger
|
||||
}
|
||||
|
||||
func (h *CgiHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
env := []string{
|
||||
"SERVER_SOFTWARE=golang",
|
||||
"SERVER_NAME=" + req.Host,
|
||||
"GATEWAY_INTERFACE=CGI/1.1",
|
||||
"REQUEST_METHOD=" + req.Method,
|
||||
"QUERY_STRING=" + req.URL.RawQuery,
|
||||
}
|
||||
|
||||
if req.ContentLength > 0 {
|
||||
env = append(env, fmt.Sprintf("CONTENT_LENGTH=%d", req.ContentLength))
|
||||
}
|
||||
if ctype, ok := req.Header["Content-Type"]; ok {
|
||||
env = append(env, "CONTENT_TYPE=" + ctype)
|
||||
}
|
||||
|
||||
// TODO: let this be cleared instead at start.
|
||||
env = append(env, os.Environ()...)
|
||||
|
||||
cwd := h.ExecutablePath
|
||||
if slash := strings.LastIndex(cwd, "/"); slash != -1 {
|
||||
cwd = cwd[0:slash]
|
||||
}
|
||||
if !strings.HasPrefix(cwd, "/") {
|
||||
cwd = "."
|
||||
}
|
||||
|
||||
cmd, err := exec.Run(
|
||||
h.ExecutablePath,
|
||||
[]string{h.ExecutablePath},
|
||||
env,
|
||||
cwd,
|
||||
exec.Pipe, // stdin
|
||||
exec.Pipe, // stdout
|
||||
exec.PassThrough, // stderr (for now)
|
||||
)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
log.Printf("CGI error: %v", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
// TODO: close subprocess somehow? No kill?!
|
||||
cmd.Stdin.Close()
|
||||
cmd.Stdout.Close()
|
||||
}()
|
||||
|
||||
if req.ContentLength != 0 {
|
||||
go func() {
|
||||
io.Copy(cmd.Stdin, req.Body)
|
||||
}()
|
||||
}
|
||||
|
||||
linebody := line.NewReader(cmd.Stdout, 1024)
|
||||
unsent := make(map[string]string)
|
||||
sentStatus := false
|
||||
for {
|
||||
line, isPrefix, err := linebody.ReadLine()
|
||||
if isPrefix {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
log.Printf("CGI: long header line from subprocess.")
|
||||
return
|
||||
}
|
||||
if err == os.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
log.Printf("CGI: error reading headers: %v", err)
|
||||
return
|
||||
}
|
||||
if len(line) == 0 {
|
||||
break
|
||||
}
|
||||
parts := strings.Split(string(line), ":", 2)
|
||||
if len(parts) < 2 {
|
||||
log.Printf("CGI: bogus header line: %s", string(line))
|
||||
continue
|
||||
}
|
||||
h, v := parts[0], parts[1]
|
||||
h = strings.TrimSpace(h)
|
||||
v = strings.TrimSpace(h)
|
||||
switch {
|
||||
case h == "Status":
|
||||
if len(v) < 3 {
|
||||
log.Printf("CGI: bogus status (short)")
|
||||
return
|
||||
}
|
||||
code, err := strconv.Atoi(v[0:3])
|
||||
if err != nil {
|
||||
log.Printf("CGI: bogus status")
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(code)
|
||||
sentStatus = true
|
||||
case sentStatus:
|
||||
rw.SetHeader(h, v)
|
||||
case !sentStatus:
|
||||
unsent[h] = v
|
||||
}
|
||||
}
|
||||
if !sentStatus {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
for h, v := range unsent {
|
||||
rw.SetHeader(h, v)
|
||||
}
|
||||
_, err = io.Copy(rw, linebody)
|
||||
if err != nil {
|
||||
log.Printf("CGI: copy error: %v", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
use strict;
|
||||
print "Content-Type: text/html\n\n";
|
||||
|
||||
print "<html><head><title>dump output</title></head><body>\n";
|
||||
|
||||
if ($ENV{'REQUEST_METHOD'} eq "GET") {
|
||||
my $in = $ENV{'QUERY_STRING'};
|
||||
print "<h2>REQUEST_METHOD was GET</h2><pre>\n";
|
||||
print "Stdin= [$in]\n";
|
||||
print "</pre>\n";
|
||||
} elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
|
||||
my $in;
|
||||
sysread(STDIN, $in, $ENV{'CONTENT_LENGTH'});
|
||||
print "<h2>REQUEST_METHOD was POST</h2><pre>\n";
|
||||
print "Stdin= [$in]\n";
|
||||
print "</pre>\n";
|
||||
}
|
||||
|
||||
print "<h2>Environment variables</h2><pre>\n";
|
||||
foreach my $key (sort(keys(%ENV))){
|
||||
print "<B>$key</B>", " "x(23-length($key)), "= $ENV{$key}\n";
|
||||
}
|
||||
|
||||
print "</pre>\n";
|
||||
|
||||
print "</body></html>\n";
|
||||
exit 0;
|
Loading…
Reference in New Issue