💾 Archived View for blinkyshark.chickenkiller.com › 2022-06-03-sss.gmi captured on 2022-06-03 at 22:56:52. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
Created 2022-06-03
I wanted to play around with the Spartan protocol. To that end, I bodged together a simple server written in Go.
It weighs in at 114 lines. It isn't complete, or pretty. Here's the code as it stands at 3 Jun 2002:
/* simple server for the spartan protocol */ package main import ( "bufio" "errors" "fmt" "net" "os" "path/filepath" "strings" ) const ( CONN_HOST = "localhost" //CONN_HOST = "0.0.0.0" //CONN_HOST = "" //CONN_HOST = "127.0.0.1" //CONN_HOST = "192.168.0.27" //CONN_HOST = "blinkyshark.chickenkiller.com" CONN_PORT = "3000" CONN_TYPE = "tcp" DATA_DIR = "/home/pi/repos/cerbo/website/gemini" ) func main() { // Listen for incoming connections. l, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT) if err != nil { fmt.Println("Error listening:", err.Error()) os.Exit(1) } // Close the listener when the application closes. defer l.Close() fmt.Println("Listening on " + CONN_HOST + ":" + CONN_PORT) for { // Listen for an incoming connection. conn, err := l.Accept() if err != nil { fmt.Println("Error accepting: ", err.Error()) os.Exit(1) } // Handle connections in a new goroutine. go handleRequest(conn) } } func get_path(conn net.Conn) (string, error) { oops := func(msg string) (string, error) { return "", errors.New(msg) } buf := make([]byte, 1024) // buffer to hold incoming data reqLen, err := conn.Read(buf) // Read the incoming connection into the buffer. //fmt.Println("reqLen:", reqLen) if err != nil { return "", err } fields := strings.Fields(string(buf[0:reqLen])) if len(fields) != 3 { return oops("bad header. Expected 3 field") } path_in := fields[1] if path_in == "/" { path_in = "index.gmi" } path, err := filepath.Abs(DATA_DIR + "/" + path_in) if err != nil { return "", err } fmt.Println("path requested:", path) // check for out-of-directory stuff if len(path) < len(DATA_DIR) { return oops("path name is too short") } if DATA_DIR != path[0:len(DATA_DIR)] { return oops("data root voilated") } info, err := os.Stat(path) if err != nil { return "", err } if info.IsDir() { return oops("Won't serve directories") } fmt.Println("server=", fields[0]) fmt.Println("loc=", path) fmt.Println("Received request ", string(buf[0:reqLen])) return path, err } // Handles incoming requests. func handleRequest(conn net.Conn) { defer conn.Close() oops := func(err_str string) { err_str = "4 " + err_str; fmt.Println(err_str) conn.Write([]byte(err_str + "\r\n")) } pathname, err := get_path(conn) if err != nil { oops(err.Error()) ; return } fp, err := os.Open(pathname) if err != nil { oops("file not found") ; return } defer fp.Close() response := "2 text/gemini" if len(pathname) < 4 || pathname[len(pathname)-4:] != ".gmi" { response = "2 text/plain; charset=utf-8" } conn.Write([]byte(response + "\r\n")) scanner := bufio.NewScanner(fp) for scanner.Scan() { txt1 := scanner.Text() //fmt.Println([]byte(txt1)) txt1 += "\r\n" conn.Write([]byte(txt1)) } if err := scanner.Err(); err != nil { oops(err.Error()) } }
No doubt that there are security improvements to be made. Just tweaks the constants at the top of the file to suit your tastes. I use the FreeBSD packet filter (pf) to forward from port 300 to 3000.
I haven't set up a server permanently, so I've not got anything for you to poke around with.
My plan is that it will also server gopher content. Gemini content, too, if I can figure out how to get server-side TLS working.
I also have in mind the idea of using groff server-side. That way I'd be able to do stuff like have text that is short, but is reformaated nicely for the different protocols. Automatically-aligned text on gopher would be a neat feature, for example.
That's it for now.