Skip to content

Instantly share code, notes, and snippets.

@russianryebread
Created July 15, 2026 06:45
Show Gist options
  • Select an option

  • Save russianryebread/decbfa694067cfeba8a59a5536cf2180 to your computer and use it in GitHub Desktop.

Select an option

Save russianryebread/decbfa694067cfeba8a59a5536cf2180 to your computer and use it in GitHub Desktop.
Simple HTTP Server
package main
import (
"fmt"
"log"
"net"
"net/http"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
)
// filteredFS wraps an http.Dir but refuses to serve hidden files.
type filteredFS struct {
root http.Dir
}
func (f filteredFS) Open(name string) (http.File, error) {
// name is the cleaned path relative to the root (http.Dir applies Clean).
// Check every component for a leading dot — reject if any is hidden.
if hasHiddenComponent(name) {
return nil, os.ErrNotExist
}
return f.root.Open(name)
}
func hasHiddenComponent(path string) bool {
for _, part := range strings.Split(path, string(os.PathSeparator)) {
if part != "" && part[0] == '.' {
return true
}
// Also reject __MACOSX / DS_Store on principle (dir starts with non-dot
// but we also reject well-known "hidden" entries).
if part == "__MACOSX" {
return true
}
}
return false
}
// responseWriter wraps http.ResponseWriter to capture the status code.
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}
// loggingMiddleware wraps an http.Handler and logs each request to stdout.
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
rw := &responseWriter{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(rw, r)
log.Printf("%s %s %d %s", r.Method, r.URL.Path, rw.status, time.Since(start))
})
}
func main() {
dir := "." // default directory
port := 8080 // default port
args := os.Args[1:]
// Parse flags — very simple flag loop.
for i := 0; i < len(args); i++ {
if args[i] == "-d" && i+1 < len(args) {
dir = args[i+1]
i++ // skip the value
} else {
// Try to parse as port
if p, err := strconv.Atoi(args[i]); err == nil && p >= 0 && p <= 65535 {
port = p
}
// Silently ignore unknown flags
}
}
absDir, err := filepath.Abs(dir)
if err != nil {
fmt.Fprintf(os.Stderr, "bad directory %q: %v\n", dir, err)
os.Exit(1)
}
if fi, err := os.Stat(absDir); err != nil || !fi.IsDir() {
fmt.Fprintf(os.Stderr, "%q is not a valid directory\n", absDir)
os.Exit(1)
}
fs := filteredFS{root: http.Dir(absDir)}
// Use a custom file server that generates a directory listing.
handler := loggingMiddleware(customFileServer(fs))
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
fmt.Fprintf(os.Stderr, "cannot listen on port %d: %v\n", port, err)
os.Exit(1)
}
fmt.Printf("Serving %s on http://localhost:%d\n", absDir, port)
if err := http.Serve(ln, handler); err != nil {
fmt.Fprintf(os.Stderr, "server error: %v\n", err)
os.Exit(1)
}
}
// customFileServer returns a handler that serves files, hides hidden paths,
// and renders a simple file-browser HTML page for directories without an
// index.html / index.htm.
func customFileServer(fs filteredFS) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Clean the path to prevent directory traversal.
cleaned := filepath.Clean(r.URL.Path)
if cleaned != r.URL.Path {
// Redirect so the browser shows the canonical path.
http.Redirect(w, r, cleaned, http.StatusMovedPermanently)
return
}
// Reject hidden paths.
if hasHiddenComponent(cleaned) {
http.NotFound(w, r)
return
}
// Build the real filesystem path.
realPath := filepath.Join(string(fs.root), cleaned)
fi, err := os.Stat(realPath)
if err != nil {
http.NotFound(w, r)
return
}
if !fi.IsDir() {
// Serve a regular file.
http.ServeFile(w, r, realPath)
return
}
// It's a directory. Check for index.html / index.htm.
for _, idx := range []string{"index.html", "index.htm"} {
idxPath := filepath.Join(realPath, idx)
if fi2, err := os.Stat(idxPath); err == nil && !fi2.IsDir() {
http.ServeFile(w, r, idxPath)
return
}
}
// No index file — render a simple directory listing.
dirListing(w, r, realPath, cleaned, fs)
})
}
func dirListing(w http.ResponseWriter, r *http.Request, realPath, urlPath string, fs filteredFS) {
entries, err := os.ReadDir(realPath)
if err != nil {
http.Error(w, "Error reading directory", http.StatusInternalServerError)
return
}
// Filter out hidden entries.
var visible []os.DirEntry
for _, e := range entries {
if !isHidden(e) {
visible = append(visible, e)
}
}
sort.Slice(visible, func(i, j int) bool {
return strings.ToLower(visible[i].Name()) < strings.ToLower(visible[j].Name())
})
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Index of %s</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; max-width: 800px; margin: 2em auto; padding: 0 1em; color: #333; }
h1 { font-size: 1.3em; border-bottom: 1px solid #ddd; padding-bottom: .3em; }
ul { list-style: none; padding: 0; }
li { padding: .3em .5em; }
li:nth-child(even) { background: #f6f6f6; }
a { text-decoration: none; color: #0366d6; }
a:hover { text-decoration: underline; }
.size { color: #666; font-size: .85em; margin-left: .6em; }
.dir { font-weight: 500; }
</style>
</head>
<body>
<h1>Index of %s</h1>
<ul>
<li><a href="../">../</a></li>
`, htmlEscape(urlPath), htmlEscape(urlPath))
// Dirs first, then files.
dirs := make([]os.DirEntry, 0)
files := make([]os.DirEntry, 0)
for _, e := range visible {
if e.IsDir() {
dirs = append(dirs, e)
} else {
files = append(files, e)
}
}
for _, e := range dirs {
name := e.Name()
fmt.Fprintf(w, `<li><a href="%s/" class="dir">%s/</a></li>
`, htmlEscape(name), htmlEscape(name))
}
for _, e := range files {
name := e.Name()
info, _ := e.Info()
size := ""
if info != nil {
size = fmt.Sprintf(`<span class="size">(%s)</span>`, formatSize(info.Size()))
}
fmt.Fprintf(w, `<li><a href="%s">%s</a>%s</li>
`, htmlEscape(name), htmlEscape(name), size)
}
fmt.Fprintf(w, `</ul>
<div style="color: #666; font-size: .85em; text-align: center;">Made in Colorado <a href="https://github.com/russianryebread" target="_blank">🏔</a></div>
</body>
</html>`)
}
func isHidden(e os.DirEntry) bool {
name := e.Name()
if name == "" {
return false
}
// Hide dotfiles and well-known metadata dirs.
if name[0] == '.' {
return true
}
if name == "__MACOSX" {
return true
}
return false
}
func formatSize(n int64) string {
switch {
case n < 1024:
return fmt.Sprintf("%d B", n)
case n < 1024*1024:
return fmt.Sprintf("%.1f KB", float64(n)/1024)
default:
return fmt.Sprintf("%.1f MB", float64(n)/(1024*1024))
}
}
func htmlEscape(s string) string {
s = strings.ReplaceAll(s, "&", "&amp;")
s = strings.ReplaceAll(s, "<", "&lt;")
s = strings.ReplaceAll(s, ">", "&gt;")
s = strings.ReplaceAll(s, `"`, "&quot;")
return s
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment