Created
July 16, 2015 11:39
-
-
Save urandom/5aa718f653b7b898b73c to your computer and use it in GitHub Desktop.
Simple static file and cgi server in go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"flag" | |
"fmt" | |
"io/ioutil" | |
"net/http" | |
"net/http/cgi" | |
"os" | |
"path" | |
"path/filepath" | |
"sort" | |
"strings" | |
"text/template" | |
"time" | |
) | |
var ( | |
port int | |
autoindex bool | |
cgiURL, cgiDir, htdocsDir string | |
staticTmpl *template.Template | |
) | |
type CGIHandler struct { | |
http.Handler | |
cgiURL, cgiDir, htdocsDir, wd string | |
} | |
type fileStats []os.FileInfo | |
type fileList struct { | |
CurDir string | |
Stats fileStats | |
} | |
func (h *CGIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
rPath := filepath.Clean(filepath.FromSlash(r.URL.Path)) | |
if len(rPath) > 0 && os.IsPathSeparator(rPath[len(rPath)-1]) { | |
rPath = rPath[:len(rPath)-1] | |
} | |
var isCGI bool | |
var scriptPath string | |
if strings.HasPrefix(path.Clean(r.URL.Path), h.cgiURL) { | |
isCGI = true | |
} | |
if isCGI { | |
scriptPath = r.URL.Path[len(h.cgiURL):] | |
if i := strings.Index(scriptPath, "/"); i > -1 { | |
scriptPath = scriptPath[:i] | |
rPath = rPath[len(h.cgiURL):] | |
rPath = rPath[:strings.Index(rPath, "/")] | |
rPath = filepath.Join(h.cgiDir, rPath) | |
} else { | |
rPath = filepath.Join(h.cgiDir, rPath[len(h.cgiURL):]) | |
} | |
scriptPath = path.Join(h.cgiURL, scriptPath) | |
} else { | |
rPath = filepath.Join(h.htdocsDir, rPath) | |
} | |
st, err := os.Stat(rPath) | |
if err != nil { | |
http.NotFound(w, r) | |
fmt.Fprintf(os.Stderr, "Error stat-ing '%s': %v\n", rPath, err) | |
return | |
} | |
if isCGI { | |
var cgih cgi.Handler | |
cgih = cgi.Handler{ | |
Path: rPath, | |
Root: scriptPath, | |
Env: []string{"DOCUMENT_ROOT=" + h.htdocsDir}, | |
} | |
cgih.ServeHTTP(w, r) | |
} else { | |
if st.IsDir() { | |
iPath := filepath.Join(rPath, "index.html") | |
st, err = os.Stat(iPath) | |
if err != nil { | |
if autoindex { | |
if !strings.HasSuffix(r.URL.Path, "/") { | |
http.Redirect(w, r, r.URL.Path+"/", http.StatusFound) | |
return | |
} | |
stats, err := ioutil.ReadDir(rPath) | |
if err == nil { | |
sort.Sort(fileStats(stats)) | |
fileList := &fileList{CurDir: path.Base(rPath), Stats: stats} | |
var buf bytes.Buffer | |
if err := staticTmpl.Execute(&buf, fileList); err != nil { | |
http.NotFound(w, r) | |
fmt.Fprintf(os.Stderr, "Error executing template: %v\n", err) | |
return | |
} | |
if _, err := buf.WriteTo(w); err != nil { | |
http.NotFound(w, r) | |
fmt.Fprintf(os.Stderr, "Error writing buffer: %v\n", err) | |
return | |
} | |
} else { | |
http.NotFound(w, r) | |
fmt.Fprintf(os.Stderr, "Error reading '%s': %v\n", iPath, err) | |
} | |
} else { | |
http.NotFound(w, r) | |
} | |
return | |
} | |
} | |
if !strings.HasPrefix(rPath, h.htdocsDir) { | |
http.NotFound(w, r) | |
return | |
} | |
http.ServeFile(w, r, rPath) | |
} | |
} | |
func (fs fileStats) Len() int { return len(fs) } | |
func (fs fileStats) Swap(i, j int) { fs[i], fs[j] = fs[j], fs[i] } | |
func (fs fileStats) Less(i, j int) bool { return fs[i].Name() < fs[j].Name() } | |
func main() { | |
wd, err := os.Getwd() | |
if err != nil { | |
fmt.Fprintf(os.Stderr, "Error getting current working dir: %v\n", err) | |
os.Exit(0) | |
} | |
cgiURL = path.Clean(cgiURL) | |
if len(cgiURL) == 0 { | |
fmt.Fprintf(os.Stderr, "No cgi url given\n") | |
os.Exit(0) | |
} | |
if cgiURL[0] != '/' { | |
cgiURL = "/" + cgiURL | |
} | |
if !strings.HasSuffix(cgiURL, "/") { | |
cgiURL = cgiURL + "/" | |
} | |
h := &CGIHandler{ | |
cgiURL: cgiURL, | |
cgiDir: filepath.Join(wd, cgiDir), | |
htdocsDir: filepath.Join(wd, htdocsDir), | |
wd: wd, | |
} | |
http.ListenAndServe(fmt.Sprintf(":%d", port), h) | |
} | |
func init() { | |
flag.IntVar(&port, "port", 8080, "port") | |
flag.BoolVar(&autoindex, "autoindex", true, "generate a directory index") | |
flag.StringVar(&cgiURL, "cgi-url", "/cgi-bin", "cgi url") | |
flag.StringVar(&cgiDir, "cgi-dir", "cgi-bin", "cgi directory") | |
flag.StringVar(&htdocsDir, "htdocs-dir", "htdocs", "htdocs directory") | |
flag.Parse() | |
if autoindex { | |
staticTmpl = template.Must(template.New("autoindex").Funcs(template.FuncMap{ | |
"formatdate": func(t time.Time) string { | |
return t.Format(dateFormat) | |
}, | |
}).Parse(fileListTemplate)) | |
} | |
} | |
const ( | |
dateFormat = "Jan 2, 2006 at 3:04pm (MST)" | |
fileListTemplate = ` | |
<!doctype html> | |
<html> | |
<head> | |
<title>{{ .CurDir }}</title> | |
</head> | |
<body> | |
<table> | |
<tbody> | |
<tr> | |
<td><a href="../">../</a></td> | |
<td colspan="2"></td> | |
</tr> | |
{{ range .Stats }} | |
<tr> | |
<td> | |
{{ if .IsDir }} | |
<a href="{{ .Name }}/">{{ .Name }}/</a> | |
{{ else }} | |
<a href="{{ .Name }}">{{ .Name }}</a> | |
{{ end }} | |
</td> | |
<td> | |
{{ .ModTime | formatdate }} | |
</td> | |
<td> | |
{{ .Size }} | |
</td> | |
</tr> | |
{{ end }} | |
</body> | |
</html> | |
` | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment