package cgi
import (
"bufio"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
)
func Request () (*http .Request , error ) {
r , err := RequestFromMap (envMap (os .Environ ()))
if err != nil {
return nil , err
}
if r .ContentLength > 0 {
r .Body = io .NopCloser (io .LimitReader (os .Stdin , r .ContentLength ))
}
return r , nil
}
func envMap(env []string ) map [string ]string {
m := make (map [string ]string )
for _ , kv := range env {
if k , v , ok := strings .Cut (kv , "=" ); ok {
m [k ] = v
}
}
return m
}
func RequestFromMap (params map [string ]string ) (*http .Request , error ) {
r := new (http .Request )
r .Method = params ["REQUEST_METHOD" ]
if r .Method == "" {
return nil , errors .New ("cgi: no REQUEST_METHOD in environment" )
}
r .Proto = params ["SERVER_PROTOCOL" ]
var ok bool
r .ProtoMajor , r .ProtoMinor , ok = http .ParseHTTPVersion (r .Proto )
if !ok {
return nil , errors .New ("cgi: invalid SERVER_PROTOCOL version" )
}
r .Close = true
r .Trailer = http .Header {}
r .Header = http .Header {}
r .Host = params ["HTTP_HOST" ]
if lenstr := params ["CONTENT_LENGTH" ]; lenstr != "" {
clen , err := strconv .ParseInt (lenstr , 10 , 64 )
if err != nil {
return nil , errors .New ("cgi: bad CONTENT_LENGTH in environment: " + lenstr )
}
r .ContentLength = clen
}
if ct := params ["CONTENT_TYPE" ]; ct != "" {
r .Header .Set ("Content-Type" , ct )
}
for k , v := range params {
if k == "HTTP_HOST" {
continue
}
if after , found := strings .CutPrefix (k , "HTTP_" ); found {
r .Header .Add (strings .ReplaceAll (after , "_" , "-" ), v )
}
}
uriStr := params ["REQUEST_URI" ]
if uriStr == "" {
uriStr = params ["SCRIPT_NAME" ] + params ["PATH_INFO" ]
s := params ["QUERY_STRING" ]
if s != "" {
uriStr += "?" + s
}
}
if s := params ["HTTPS" ]; s == "on" || s == "ON" || s == "1" {
r .TLS = &tls .ConnectionState {HandshakeComplete : true }
}
if r .Host != "" {
rawurl := r .Host + uriStr
if r .TLS == nil {
rawurl = "http://" + rawurl
} else {
rawurl = "https://" + rawurl
}
url , err := url .Parse (rawurl )
if err != nil {
return nil , errors .New ("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl )
}
r .URL = url
}
if r .URL == nil {
url , err := url .Parse (uriStr )
if err != nil {
return nil , errors .New ("cgi: failed to parse REQUEST_URI into a URL: " + uriStr )
}
r .URL = url
}
remotePort , _ := strconv .Atoi (params ["REMOTE_PORT" ])
r .RemoteAddr = net .JoinHostPort (params ["REMOTE_ADDR" ], strconv .Itoa (remotePort ))
return r , nil
}
func Serve (handler http .Handler ) error {
req , err := Request ()
if err != nil {
return err
}
if req .Body == nil {
req .Body = http .NoBody
}
if handler == nil {
handler = http .DefaultServeMux
}
rw := &response {
req : req ,
header : make (http .Header ),
bufw : bufio .NewWriter (os .Stdout ),
}
handler .ServeHTTP (rw , req )
rw .Write (nil )
if err = rw .bufw .Flush (); err != nil {
return err
}
return nil
}
type response struct {
req *http .Request
header http .Header
code int
wroteHeader bool
wroteCGIHeader bool
bufw *bufio .Writer
}
func (r *response ) Flush () {
r .bufw .Flush ()
}
func (r *response ) Header () http .Header {
return r .header
}
func (r *response ) Write (p []byte ) (n int , err error ) {
if !r .wroteHeader {
r .WriteHeader (http .StatusOK )
}
if !r .wroteCGIHeader {
r .writeCGIHeader (p )
}
return r .bufw .Write (p )
}
func (r *response ) WriteHeader (code int ) {
if r .wroteHeader {
fmt .Fprintf (os .Stderr , "CGI attempted to write header twice on request for %s" , r .req .URL )
return
}
r .wroteHeader = true
r .code = code
}
func (r *response ) writeCGIHeader (p []byte ) {
if r .wroteCGIHeader {
return
}
r .wroteCGIHeader = true
fmt .Fprintf (r .bufw , "Status: %d %s\r\n" , r .code , http .StatusText (r .code ))
if _ , hasType := r .header ["Content-Type" ]; !hasType {
r .header .Set ("Content-Type" , http .DetectContentType (p ))
}
r .header .Write (r .bufw )
r .bufw .WriteString ("\r\n" )
r .bufw .Flush ()
}
The pages are generated with Golds v0.7.0-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 .