1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // This file implements CGI from the perspective of a child
24 // Request returns the HTTP request as represented in the current
25 // environment. This assumes the current program is being run
26 // by a web server in a CGI environment.
27 // The returned Request's Body is populated, if applicable.
28 func Request() (*http.Request, os.Error) {
29 r, err := RequestFromMap(envMap(os.Environ()))
33 if r.ContentLength > 0 {
34 r.Body = ioutil.NopCloser(io.LimitReader(os.Stdin, r.ContentLength))
39 func envMap(env []string) map[string]string {
40 m := make(map[string]string)
41 for _, kv := range env {
42 if idx := strings.Index(kv, "="); idx != -1 {
43 m[kv[:idx]] = kv[idx+1:]
49 // RequestFromMap creates an http.Request from CGI variables.
50 // The returned Request's Body field is not populated.
51 func RequestFromMap(params map[string]string) (*http.Request, os.Error) {
52 r := new(http.Request)
53 r.Method = params["REQUEST_METHOD"]
55 return nil, os.NewError("cgi: no REQUEST_METHOD in environment")
58 r.Proto = params["SERVER_PROTOCOL"]
60 r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto)
62 return nil, os.NewError("cgi: invalid SERVER_PROTOCOL version")
66 r.Trailer = http.Header{}
67 r.Header = http.Header{}
69 r.Host = params["HTTP_HOST"]
71 if lenstr := params["CONTENT_LENGTH"]; lenstr != "" {
72 clen, err := strconv.Atoi64(lenstr)
74 return nil, os.NewError("cgi: bad CONTENT_LENGTH in environment: " + lenstr)
76 r.ContentLength = clen
79 if ct := params["CONTENT_TYPE"]; ct != "" {
80 r.Header.Set("Content-Type", ct)
83 // Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers
84 for k, v := range params {
85 if !strings.HasPrefix(k, "HTTP_") || k == "HTTP_HOST" {
88 r.Header.Add(strings.Replace(k[5:], "_", "-", -1), v)
91 // TODO: cookies. parsing them isn't exported, though.
94 // Hostname is provided, so we can reasonably construct a URL,
95 // even if we have to assume 'http' for the scheme.
96 r.RawURL = "http://" + r.Host + params["REQUEST_URI"]
97 url, err := url.Parse(r.RawURL)
99 return nil, os.NewError("cgi: failed to parse host and REQUEST_URI into a URL: " + r.RawURL)
103 // Fallback logic if we don't have a Host header or the URL
106 r.RawURL = params["REQUEST_URI"]
107 url, err := url.Parse(r.RawURL)
109 return nil, os.NewError("cgi: failed to parse REQUEST_URI into a URL: " + r.RawURL)
114 // There's apparently a de-facto standard for this.
115 // http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636
116 if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" {
117 r.TLS = &tls.ConnectionState{HandshakeComplete: true}
120 // Request.RemoteAddr has its port set by Go's standard http
121 // server, so we do here too. We don't have one, though, so we
123 r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], "0")
128 // Serve executes the provided Handler on the currently active CGI
129 // request, if any. If there's no current CGI environment
130 // an error is returned. The provided handler may be nil to use
131 // http.DefaultServeMux.
132 func Serve(handler http.Handler) os.Error {
133 req, err := Request()
138 handler = http.DefaultServeMux
142 header: make(http.Header),
143 bufw: bufio.NewWriter(os.Stdout),
145 handler.ServeHTTP(rw, req)
146 if err = rw.bufw.Flush(); err != nil {
152 type response struct {
159 func (r *response) Flush() {
163 func (r *response) Header() http.Header {
167 func (r *response) Write(p []byte) (n int, err os.Error) {
169 r.WriteHeader(http.StatusOK)
171 return r.bufw.Write(p)
174 func (r *response) WriteHeader(code int) {
176 // Note: explicitly using Stderr, as Stdout is our HTTP output.
177 fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL)
181 fmt.Fprintf(r.bufw, "Status: %d %s\r\n", code, http.StatusText(code))
183 // Set a default Content-Type
184 if _, hasType := r.header["Content-Type"]; !hasType {
185 r.header.Add("Content-Type", "text/html; charset=utf-8")
188 r.header.Write(r.bufw)
189 r.bufw.WriteString("\r\n")