// 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 cgiimport ()// 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.// The returned Request's Body is populated, if applicable.func () (*http.Request, error) { , := RequestFromMap(envMap(os.Environ()))if != nil {returnnil, }if .ContentLength > 0 { .Body = io.NopCloser(io.LimitReader(os.Stdin, .ContentLength)) }return , nil}func envMap( []string) map[string]string { := make(map[string]string)for , := range {if , , := strings.Cut(, "="); { [] = } }return}// RequestFromMap creates an [http.Request] from CGI variables.// The returned Request's Body field is not populated.func ( map[string]string) (*http.Request, error) { := new(http.Request) .Method = ["REQUEST_METHOD"]if .Method == "" {returnnil, errors.New("cgi: no REQUEST_METHOD in environment") } .Proto = ["SERVER_PROTOCOL"]varboolif .Proto == "INCLUDED" {// SSI (Server Side Include) use case // CGI Specification RFC 3875 - section 4.1.16 .ProtoMajor, .ProtoMinor = 1, 0 } elseif .ProtoMajor, .ProtoMinor, = http.ParseHTTPVersion(.Proto); ! {returnnil, errors.New("cgi: invalid SERVER_PROTOCOL version") } .Close = true .Trailer = http.Header{} .Header = http.Header{} .Host = ["HTTP_HOST"]if := ["CONTENT_LENGTH"]; != "" { , := strconv.ParseInt(, 10, 64)if != nil {returnnil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + ) } .ContentLength = }if := ["CONTENT_TYPE"]; != "" { .Header.Set("Content-Type", ) }// Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headersfor , := range {if == "HTTP_HOST" {continue }if , := strings.CutPrefix(, "HTTP_"); { .Header.Add(strings.ReplaceAll(, "_", "-"), ) } } := ["REQUEST_URI"]if == "" {// Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING. = ["SCRIPT_NAME"] + ["PATH_INFO"] := ["QUERY_STRING"]if != "" { += "?" + } }// There's apparently a de-facto standard for this. // https://web.archive.org/web/20170105004655/http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636if := ["HTTPS"]; == "on" || == "ON" || == "1" { .TLS = &tls.ConnectionState{HandshakeComplete: true} }if .Host != "" {// Hostname is provided, so we can reasonably construct a URL. := .Host + if .TLS == nil { = "http://" + } else { = "https://" + } , := url.Parse()if != nil {returnnil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + ) } .URL = }// Fallback logic if we don't have a Host header or the URL // failed to parseif .URL == nil { , := url.Parse()if != nil {returnnil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + ) } .URL = }// Request.RemoteAddr has its port set by Go's standard http // server, so we do here too. , := strconv.Atoi(["REMOTE_PORT"]) // zero if unset or invalid .RemoteAddr = net.JoinHostPort(["REMOTE_ADDR"], strconv.Itoa())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 ( http.Handler) error { , := Request()if != nil {return }if .Body == nil { .Body = http.NoBody }if == nil { = http.DefaultServeMux } := &response{req: ,header: make(http.Header),bufw: bufio.NewWriter(os.Stdout), } .ServeHTTP(, ) .Write(nil) // make sure a response is sentif = .bufw.Flush(); != nil {return }returnnil}type response struct { req *http.Request header http.Header code int wroteHeader bool wroteCGIHeader bool bufw *bufio.Writer}func ( *response) () { .bufw.Flush()}func ( *response) () http.Header {return .header}func ( *response) ( []byte) ( int, error) {if !.wroteHeader { .WriteHeader(http.StatusOK) }if !.wroteCGIHeader { .writeCGIHeader() }return .bufw.Write()}func ( *response) ( int) {if .wroteHeader {// Note: explicitly using Stderr, as Stdout is our HTTP output.fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", .req.URL)return } .wroteHeader = true .code = }// writeCGIHeader finalizes the header sent to the client and writes it to the output.// p is not written by writeHeader, but is the first chunk of the body// that will be written. It is sniffed for a Content-Type if none is// set explicitly.func ( *response) ( []byte) {if .wroteCGIHeader {return } .wroteCGIHeader = truefmt.Fprintf(.bufw, "Status: %d %s\r\n", .code, http.StatusText(.code))if , := .header["Content-Type"]; ! { .header.Set("Content-Type", http.DetectContentType()) } .header.Write(.bufw) .bufw.WriteString("\r\n") .bufw.Flush()}
The pages are generated with Goldsv0.8.3-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.