mirror of
https://github.com/golang/go
synced 2024-11-25 03:57:56 -07:00
cgi: child support (e.g. Go CGI under Apache)
The http/cgi package now supports both being a CGI host or being a CGI child process. R=rsc, adg, bradfitzwork CC=golang-dev https://golang.org/cl/4245070
This commit is contained in:
parent
8c76218f89
commit
d64a18a27e
@ -6,6 +6,7 @@ include ../../../Make.inc
|
|||||||
|
|
||||||
TARG=http/cgi
|
TARG=http/cgi
|
||||||
GOFILES=\
|
GOFILES=\
|
||||||
cgi.go\
|
child.go\
|
||||||
|
host.go\
|
||||||
|
|
||||||
include ../../../Make.pkg
|
include ../../../Make.pkg
|
||||||
|
202
src/pkg/http/cgi/child.go
Normal file
202
src/pkg/http/cgi/child.go
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file implements CGI from the perspective of a child
|
||||||
|
// process.
|
||||||
|
|
||||||
|
package cgi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"http"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request returns the HTTP request as represented in the current
|
||||||
|
// environment. This assumes the current program is being run
|
||||||
|
// by a web server in a CGI environment.
|
||||||
|
func Request() (*http.Request, os.Error) {
|
||||||
|
return requestFromEnvironment(envMap(os.Environ()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func envMap(env []string) map[string]string {
|
||||||
|
m := make(map[string]string)
|
||||||
|
for _, kv := range env {
|
||||||
|
if idx := strings.Index(kv, "="); idx != -1 {
|
||||||
|
m[kv[:idx]] = kv[idx+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// These environment variables are manually copied into Request
|
||||||
|
var skipHeader = map[string]bool{
|
||||||
|
"HTTP_HOST": true,
|
||||||
|
"HTTP_REFERER": true,
|
||||||
|
"HTTP_USER_AGENT": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestFromEnvironment(env map[string]string) (*http.Request, os.Error) {
|
||||||
|
r := new(http.Request)
|
||||||
|
r.Method = env["REQUEST_METHOD"]
|
||||||
|
if r.Method == "" {
|
||||||
|
return nil, os.NewError("cgi: no REQUEST_METHOD in environment")
|
||||||
|
}
|
||||||
|
r.Close = true
|
||||||
|
r.Trailer = http.Header{}
|
||||||
|
r.Header = http.Header{}
|
||||||
|
|
||||||
|
r.Host = env["HTTP_HOST"]
|
||||||
|
r.Referer = env["HTTP_REFERER"]
|
||||||
|
r.UserAgent = env["HTTP_USER_AGENT"]
|
||||||
|
|
||||||
|
// CGI doesn't allow chunked requests, so these should all be accurate:
|
||||||
|
r.Proto = "HTTP/1.0"
|
||||||
|
r.ProtoMajor = 1
|
||||||
|
r.ProtoMinor = 0
|
||||||
|
r.TransferEncoding = nil
|
||||||
|
|
||||||
|
if lenstr := env["CONTENT_LENGTH"]; lenstr != "" {
|
||||||
|
clen, err := strconv.Atoi64(lenstr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewError("cgi: bad CONTENT_LENGTH in environment: " + lenstr)
|
||||||
|
}
|
||||||
|
r.ContentLength = clen
|
||||||
|
r.Body = nopCloser{io.LimitReader(os.Stdin, clen)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers
|
||||||
|
for k, v := range env {
|
||||||
|
if !strings.HasPrefix(k, "HTTP_") || skipHeader[k] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.Header.Add(strings.Replace(k[5:], "_", "-", -1), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: cookies. parsing them isn't exported, though.
|
||||||
|
|
||||||
|
if r.Host != "" {
|
||||||
|
// Hostname is provided, so we can reasonably construct a URL,
|
||||||
|
// even if we have to assume 'http' for the scheme.
|
||||||
|
r.RawURL = "http://" + r.Host + env["REQUEST_URI"]
|
||||||
|
url, err := http.ParseURL(r.RawURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewError("cgi: failed to parse host and REQUEST_URI into a URL: " + r.RawURL)
|
||||||
|
}
|
||||||
|
r.URL = url
|
||||||
|
}
|
||||||
|
// Fallback logic if we don't have a Host header or the URL
|
||||||
|
// failed to parse
|
||||||
|
if r.URL == nil {
|
||||||
|
r.RawURL = env["REQUEST_URI"]
|
||||||
|
url, err := http.ParseURL(r.RawURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewError("cgi: failed to parse REQUEST_URI into a URL: " + r.RawURL)
|
||||||
|
}
|
||||||
|
r.URL = url
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move this to ioutil or something. It's copy/pasted way too often.
|
||||||
|
type nopCloser struct {
|
||||||
|
io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nopCloser) Close() os.Error { return nil }
|
||||||
|
|
||||||
|
// Serve executes the provided Handler on the currently active CGI
|
||||||
|
// request, if any. If there's no current CGI environment
|
||||||
|
// an error is returned. The provided handler may be nil to use
|
||||||
|
// http.DefaultServeMux.
|
||||||
|
func Serve(handler http.Handler) os.Error {
|
||||||
|
req, err := Request()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if handler == nil {
|
||||||
|
handler = http.DefaultServeMux
|
||||||
|
}
|
||||||
|
rw := &response{
|
||||||
|
req: req,
|
||||||
|
header: make(http.Header),
|
||||||
|
bufw: bufio.NewWriter(os.Stdout),
|
||||||
|
}
|
||||||
|
handler.ServeHTTP(rw, req)
|
||||||
|
if err = rw.bufw.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
req *http.Request
|
||||||
|
header http.Header
|
||||||
|
bufw *bufio.Writer
|
||||||
|
headerSent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) Flush() {
|
||||||
|
r.bufw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) RemoteAddr() string {
|
||||||
|
return os.Getenv("REMOTE_ADDR")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) SetHeader(k, v string) {
|
||||||
|
if v == "" {
|
||||||
|
r.header.Del(k)
|
||||||
|
} else {
|
||||||
|
r.header.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) Write(p []byte) (n int, err os.Error) {
|
||||||
|
if !r.headerSent {
|
||||||
|
r.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
return r.bufw.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) WriteHeader(code int) {
|
||||||
|
if r.headerSent {
|
||||||
|
// Note: explicitly using Stderr, as Stdout is our HTTP output.
|
||||||
|
fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.headerSent = true
|
||||||
|
fmt.Fprintf(r.bufw, "Status: %d %s\r\n", code, http.StatusText(code))
|
||||||
|
|
||||||
|
// Set a default Content-Type
|
||||||
|
if _, hasType := r.header["Content-Type"]; !hasType {
|
||||||
|
r.header.Add("Content-Type", "text/html; charset=utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add a method on http.Header to write itself to an io.Writer?
|
||||||
|
// This is duplicated code.
|
||||||
|
for k, vv := range r.header {
|
||||||
|
for _, v := range vv {
|
||||||
|
v = strings.Replace(v, "\n", "", -1)
|
||||||
|
v = strings.Replace(v, "\r", "", -1)
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
fmt.Fprintf(r.bufw, "%s: %s\r\n", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.bufw.Write([]byte("\r\n"))
|
||||||
|
r.bufw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) UsingTLS() bool {
|
||||||
|
// There's apparently a de-facto standard for this.
|
||||||
|
// http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636
|
||||||
|
if s := os.Getenv("HTTPS"); s == "on" || s == "ON" || s == "1" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
83
src/pkg/http/cgi/child_test.go
Normal file
83
src/pkg/http/cgi/child_test.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Tests for CGI (the child process perspective)
|
||||||
|
|
||||||
|
package cgi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRequest(t *testing.T) {
|
||||||
|
env := map[string]string{
|
||||||
|
"REQUEST_METHOD": "GET",
|
||||||
|
"HTTP_HOST": "example.com",
|
||||||
|
"HTTP_REFERER": "elsewhere",
|
||||||
|
"HTTP_USER_AGENT": "goclient",
|
||||||
|
"HTTP_FOO_BAR": "baz",
|
||||||
|
"REQUEST_URI": "/path?a=b",
|
||||||
|
"CONTENT_LENGTH": "123",
|
||||||
|
}
|
||||||
|
req, err := requestFromEnvironment(env)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("requestFromEnvironment: %v", err)
|
||||||
|
}
|
||||||
|
if g, e := req.UserAgent, "goclient"; e != g {
|
||||||
|
t.Errorf("expected UserAgent %q; got %q", e, g)
|
||||||
|
}
|
||||||
|
if g, e := req.Method, "GET"; e != g {
|
||||||
|
t.Errorf("expected Method %q; got %q", e, g)
|
||||||
|
}
|
||||||
|
if g, e := req.Header.Get("User-Agent"), ""; e != g {
|
||||||
|
// Tests that we don't put recognized headers in the map
|
||||||
|
t.Errorf("expected User-Agent %q; got %q", e, g)
|
||||||
|
}
|
||||||
|
if g, e := req.ContentLength, int64(123); e != g {
|
||||||
|
t.Errorf("expected ContentLength %d; got %d", e, g)
|
||||||
|
}
|
||||||
|
if g, e := req.Referer, "elsewhere"; e != g {
|
||||||
|
t.Errorf("expected Referer %q; got %q", e, g)
|
||||||
|
}
|
||||||
|
if req.Header == nil {
|
||||||
|
t.Fatalf("unexpected nil Header")
|
||||||
|
}
|
||||||
|
if g, e := req.Header.Get("Foo-Bar"), "baz"; e != g {
|
||||||
|
t.Errorf("expected Foo-Bar %q; got %q", e, g)
|
||||||
|
}
|
||||||
|
if g, e := req.RawURL, "http://example.com/path?a=b"; e != g {
|
||||||
|
t.Errorf("expected RawURL %q; got %q", e, g)
|
||||||
|
}
|
||||||
|
if g, e := req.URL.String(), "http://example.com/path?a=b"; e != g {
|
||||||
|
t.Errorf("expected URL %q; got %q", e, g)
|
||||||
|
}
|
||||||
|
if g, e := req.FormValue("a"), "b"; e != g {
|
||||||
|
t.Errorf("expected FormValue(a) %q; got %q", e, g)
|
||||||
|
}
|
||||||
|
if req.Trailer == nil {
|
||||||
|
t.Errorf("unexpected nil Trailer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequestWithoutHost(t *testing.T) {
|
||||||
|
env := map[string]string{
|
||||||
|
"HTTP_HOST": "",
|
||||||
|
"REQUEST_METHOD": "GET",
|
||||||
|
"REQUEST_URI": "/path?a=b",
|
||||||
|
"CONTENT_LENGTH": "123",
|
||||||
|
}
|
||||||
|
req, err := requestFromEnvironment(env)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("requestFromEnvironment: %v", err)
|
||||||
|
}
|
||||||
|
if g, e := req.RawURL, "/path?a=b"; e != g {
|
||||||
|
t.Errorf("expected RawURL %q; got %q", e, g)
|
||||||
|
}
|
||||||
|
if req.URL == nil {
|
||||||
|
t.Fatalf("unexpected nil URL")
|
||||||
|
}
|
||||||
|
if g, e := req.URL.String(), "/path?a=b"; e != g {
|
||||||
|
t.Errorf("expected URL %q; got %q", e, g)
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,9 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file implements the host side of CGI (being the webserver
|
||||||
|
// parent process).
|
||||||
|
|
||||||
// Package cgi implements CGI (Common Gateway Interface) as specified
|
// Package cgi implements CGI (Common Gateway Interface) as specified
|
||||||
// in RFC 3875.
|
// in RFC 3875.
|
||||||
//
|
//
|
||||||
@ -12,6 +15,7 @@
|
|||||||
package cgi
|
package cgi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/line"
|
"encoding/line"
|
||||||
"exec"
|
"exec"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -19,7 +23,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -31,8 +35,10 @@ var trailingPort = regexp.MustCompile(`:([0-9]+)$`)
|
|||||||
type Handler struct {
|
type Handler struct {
|
||||||
Path string // path to the CGI executable
|
Path string // path to the CGI executable
|
||||||
Root string // root URI prefix of handler or empty for "/"
|
Root string // root URI prefix of handler or empty for "/"
|
||||||
|
|
||||||
Env []string // extra environment variables to set, if any
|
Env []string // extra environment variables to set, if any
|
||||||
Logger *log.Logger // optional log for errors or nil to use log.Print
|
Logger *log.Logger // optional log for errors or nil to use log.Print
|
||||||
|
Args []string // optional arguments to pass to child process
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
@ -73,9 +79,20 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
"SERVER_PORT=" + port,
|
"SERVER_PORT=" + port,
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, _ := range req.Header {
|
if len(req.Cookie) > 0 {
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
for idx, c := range req.Cookie {
|
||||||
|
if idx > 0 {
|
||||||
|
b.Write([]byte("; "))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "%s=%s", c.Name, c.Value)
|
||||||
|
}
|
||||||
|
env = append(env, "HTTP_COOKIE="+b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range req.Header {
|
||||||
k = strings.Map(upperCaseAndUnderscore, k)
|
k = strings.Map(upperCaseAndUnderscore, k)
|
||||||
env = append(env, "HTTP_"+k+"="+req.Header.Get(k))
|
env = append(env, "HTTP_"+k+"="+strings.Join(v, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.ContentLength > 0 {
|
if req.ContentLength > 0 {
|
||||||
@ -89,15 +106,17 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
env = append(env, h.Env...)
|
env = append(env, h.Env...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use filepath instead of path when available
|
cwd, pathBase := filepath.Split(h.Path)
|
||||||
cwd, pathBase := path.Split(h.Path)
|
|
||||||
if cwd == "" {
|
if cwd == "" {
|
||||||
cwd = "."
|
cwd = "."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args := []string{h.Path}
|
||||||
|
args = append(args, h.Args...)
|
||||||
|
|
||||||
cmd, err := exec.Run(
|
cmd, err := exec.Run(
|
||||||
pathBase,
|
pathBase,
|
||||||
[]string{h.Path},
|
args,
|
||||||
env,
|
env,
|
||||||
cwd,
|
cwd,
|
||||||
exec.Pipe, // stdin
|
exec.Pipe, // stdin
|
@ -176,6 +176,28 @@ func TestPathInfoDirRoot(t *testing.T) {
|
|||||||
runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
|
runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDupHeaders(t *testing.T) {
|
||||||
|
if skipTest(t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h := &Handler{
|
||||||
|
Path: "testdata/test.cgi",
|
||||||
|
}
|
||||||
|
expectedMap := map[string]string{
|
||||||
|
"env-REQUEST_URI": "/myscript/bar?a=b",
|
||||||
|
"env-SCRIPT_FILENAME": "testdata/test.cgi",
|
||||||
|
"env-HTTP_COOKIE": "nom=NOM; yum=YUM",
|
||||||
|
"env-HTTP_X_FOO": "val1, val2",
|
||||||
|
}
|
||||||
|
runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+
|
||||||
|
"Cookie: nom=NOM\n"+
|
||||||
|
"Cookie: yum=YUM\n"+
|
||||||
|
"X-Foo: val1\n"+
|
||||||
|
"X-Foo: val2\n"+
|
||||||
|
"Host: example.com\n\n",
|
||||||
|
expectedMap)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPathInfoNoRoot(t *testing.T) {
|
func TestPathInfoNoRoot(t *testing.T) {
|
||||||
if skipTest(t) {
|
if skipTest(t) {
|
||||||
return
|
return
|
74
src/pkg/http/cgi/matryoshka_test.go
Normal file
74
src/pkg/http/cgi/matryoshka_test.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Tests a Go CGI program running under a Go CGI host process.
|
||||||
|
// Further, the two programs are the same binary, just checking
|
||||||
|
// their environment to figure out what mode to run in.
|
||||||
|
|
||||||
|
package cgi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This test is a CGI host (testing host.go) that runs its own binary
|
||||||
|
// as a child process testing the other half of CGI (child.go).
|
||||||
|
func TestHostingOurselves(t *testing.T) {
|
||||||
|
h := &Handler{
|
||||||
|
Path: os.Args[0],
|
||||||
|
Root: "/test.go",
|
||||||
|
Args: []string{"-test.run=TestBeChildCGIProcess"},
|
||||||
|
}
|
||||||
|
expectedMap := map[string]string{
|
||||||
|
"test": "Hello CGI-in-CGI",
|
||||||
|
"param-a": "b",
|
||||||
|
"param-foo": "bar",
|
||||||
|
"env-GATEWAY_INTERFACE": "CGI/1.1",
|
||||||
|
"env-HTTP_HOST": "example.com",
|
||||||
|
"env-PATH_INFO": "",
|
||||||
|
"env-QUERY_STRING": "foo=bar&a=b",
|
||||||
|
"env-REMOTE_ADDR": "1.2.3.4",
|
||||||
|
"env-REMOTE_HOST": "1.2.3.4",
|
||||||
|
"env-REQUEST_METHOD": "GET",
|
||||||
|
"env-REQUEST_URI": "/test.go?foo=bar&a=b",
|
||||||
|
"env-SCRIPT_FILENAME": os.Args[0],
|
||||||
|
"env-SCRIPT_NAME": "/test.go",
|
||||||
|
"env-SERVER_NAME": "example.com",
|
||||||
|
"env-SERVER_PORT": "80",
|
||||||
|
"env-SERVER_SOFTWARE": "go",
|
||||||
|
}
|
||||||
|
replay := runCgiTest(t, h, "GET /test.go?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
|
||||||
|
|
||||||
|
if expected, got := "text/html; charset=utf-8", replay.Header.Get("Content-Type"); got != expected {
|
||||||
|
t.Errorf("got a Content-Type of %q; expected %q", got, expected)
|
||||||
|
}
|
||||||
|
if expected, got := "X-Test-Value", replay.Header.Get("X-Test-Header"); got != expected {
|
||||||
|
t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: not actually a test.
|
||||||
|
func TestBeChildCGIProcess(t *testing.T) {
|
||||||
|
if os.Getenv("REQUEST_METHOD") == "" {
|
||||||
|
// Not in a CGI environment; skipping test.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
rw.SetHeader("X-Test-Header", "X-Test-Value")
|
||||||
|
fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n")
|
||||||
|
req.ParseForm()
|
||||||
|
for k, vv := range req.Form {
|
||||||
|
for _, v := range vv {
|
||||||
|
fmt.Fprintf(rw, "param-%s=%s\n", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, kv := range os.Environ() {
|
||||||
|
fmt.Fprintf(rw, "env-%s\n", kv)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
2
src/pkg/http/cgi/testdata/test.cgi
vendored
2
src/pkg/http/cgi/testdata/test.cgi
vendored
@ -12,7 +12,7 @@ my $q = CGI->new;
|
|||||||
my $params = $q->Vars;
|
my $params = $q->Vars;
|
||||||
|
|
||||||
my $NL = "\r\n";
|
my $NL = "\r\n";
|
||||||
$NL = "\n" if 1 || $params->{mode} eq "NL";
|
$NL = "\n" if $params->{mode} eq "NL";
|
||||||
|
|
||||||
my $p = sub {
|
my $p = sub {
|
||||||
print "$_[0]$NL";
|
print "$_[0]$NL";
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
// TODO(rsc):
|
// TODO(rsc):
|
||||||
// logging
|
// logging
|
||||||
// cgi support
|
|
||||||
// post support
|
// post support
|
||||||
|
|
||||||
package http
|
package http
|
||||||
|
Loading…
Reference in New Issue
Block a user