diff options
| author | tjpcc <tjp@ctrl-c.club> | 2023-01-09 16:40:24 -0700 | 
|---|---|---|
| committer | tjpcc <tjp@ctrl-c.club> | 2023-01-09 16:40:24 -0700 | 
| commit | ff05d62013906f3086b452bfeda3e0d5b9b7a541 (patch) | |
| tree | 3be29de0b1bc7c273041c6d89b71ca447c940556 /examples | |
Initial commit.
some basics:
 - minimal README
 - some TODOs
 - server and request handler framework
 - contribs: file serving, request logging
 - server examples
 - CI setup
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/cowsay/main.go | 99 | ||||
| -rw-r--r-- | examples/fileserver/main.go | 60 | ||||
| -rw-r--r-- | examples/inspectls/main.go | 95 | 
3 files changed, 254 insertions, 0 deletions
diff --git a/examples/cowsay/main.go b/examples/cowsay/main.go new file mode 100644 index 0000000..e724421 --- /dev/null +++ b/examples/cowsay/main.go @@ -0,0 +1,99 @@ +package main + +import ( +	"bytes" +	"context" +	"io" +	"log" +	"net" +	"os" +	"os/exec" + +	guslog "tildegit.org/tjp/gus/contrib/log" +	"tildegit.org/tjp/gus/gemini" +) + +func main() { +	// Get TLS files from the environment +	certfile, keyfile := envConfig() + +	// build a TLS configuration suitable for gemini +	tlsconf, err := gemini.FileTLS(certfile, keyfile) +	if err != nil { +		log.Fatal(err) +	} + +	// set up the network listener +	listener, err := net.Listen("tcp4", ":1965") +	if err != nil { +		log.Fatal(err) +	} + +	// add request logging to the request handler +	handler := guslog.Requests(os.Stdout, nil)(cowsayHandler) + +	// run the server +	gemini.NewServer(context.Background(), tlsconf, listener, handler).Serve() +} + +func cowsayHandler(ctx context.Context, req *gemini.Request) *gemini.Response { +	// prompt for a query if there is none already +	if req.RawQuery == "" { +		return gemini.Input("enter a phrase") +	} + +	// find the "cowsay" executable +	binpath, err := exec.LookPath("cowsay") +	if err != nil { +		return gemini.Failure(err) +	} + +	// build the command and set the query to be passed to its stdin +	cmd := exec.CommandContext(ctx, binpath) +	cmd.Stdin = bytes.NewBufferString(req.UnescapedQuery()) + +	// set up a pipe so we can read the command's stdout +	rd, err := cmd.StdoutPipe() +	if err != nil { +		return gemini.Failure(err) +	} + +	// start the command +	if err := cmd.Start(); err != nil { +		return gemini.Failure(err) +	} + +	// read the complete stdout contents, clean up the process on error +	buf, err := io.ReadAll(rd) +	if err != nil { +		cmd.Process.Kill() +		cmd.Wait() +		return gemini.Failure(err) +	} + +	// wait for the process to close +	cmd.Wait() + +	// pass the buffer to the response wrapped in ``` toggles, +	// and include a link to start over +	out := io.MultiReader( +		bytes.NewBufferString("```\n"), +		bytes.NewBuffer(buf), +		bytes.NewBufferString("\n```\n=> . again"), +	) +	return gemini.Success("text/gemini", out) +} + +func envConfig() (string, string) { +	certfile, ok := os.LookupEnv("SERVER_CERTIFICATE") +	if !ok { +		log.Fatal("missing SERVER_CERTIFICATE environment variable") +	} + +	keyfile, ok := os.LookupEnv("SERVER_PRIVATEKEY") +	if !ok { +		log.Fatal("missing SERVER_PRIVATEKEY environment variable") +	} + +	return certfile, keyfile +} diff --git a/examples/fileserver/main.go b/examples/fileserver/main.go new file mode 100644 index 0000000..01d22ee --- /dev/null +++ b/examples/fileserver/main.go @@ -0,0 +1,60 @@ +package main + +import ( +	"context" +	"log" +	"net" +	"os" + +	"tildegit.org/tjp/gus/contrib/fs" +	guslog "tildegit.org/tjp/gus/contrib/log" +	"tildegit.org/tjp/gus/gemini" +) + +func main() { +	// Get TLS files from the environment +	certfile, keyfile := envConfig() + +	// build a TLS configuration suitable for gemini +	tlsconf, err := gemini.FileTLS(certfile, keyfile) +	if err != nil { +		log.Fatal(err) +	} + +	// set up the network listen +	listener, err := net.Listen("tcp4", ":1965") +	if err != nil { +		log.Fatal(err) +	} + +	// build the request handler +	fileSystem := os.DirFS(".") +	// Fallthrough tries each handler in succession until it gets something other than "51 Not Found" +	handler := gemini.Fallthrough( +		// first see if they're fetching a directory and we have <dir>/index.gmi +		fs.DirectoryDefault(fileSystem, "index.gmi"), +		// next (still if they requested a directory) build a directory listing response +		fs.DirectoryListing(fileSystem, nil), +		// finally, try to find a file at the request path and respond with that +		fs.FileHandler(fileSystem), +	) +	// add request logging to stdout +	handler = guslog.Requests(os.Stdout, nil)(handler) + +	// run the server +	gemini.NewServer(context.Background(), tlsconf, listener, handler).Serve() +} + +func envConfig() (string, string) { +	certfile, ok := os.LookupEnv("SERVER_CERTIFICATE") +	if !ok { +		log.Fatal("missing SERVER_CERTIFICATE environment variable") +	} + +	keyfile, ok := os.LookupEnv("SERVER_PRIVATEKEY") +	if !ok { +		log.Fatal("missing SERVER_PRIVATEKEY environment variable") +	} + +	return certfile, keyfile +} diff --git a/examples/inspectls/main.go b/examples/inspectls/main.go new file mode 100644 index 0000000..a315e40 --- /dev/null +++ b/examples/inspectls/main.go @@ -0,0 +1,95 @@ +package main + +import ( +	"bytes" +	"context" +	"crypto/md5" +	"crypto/tls" +	"crypto/x509" +	"encoding/hex" +	"fmt" +	"log" +	"net" +	"os" +	"strings" + +	"tildegit.org/tjp/gus/gemini" +	guslog "tildegit.org/tjp/gus/contrib/log" +) + +func main() { +	// Get TLS files from the environment +	certfile, keyfile := envConfig() + +	// build a TLS configuration suitable for gemini +	tlsconf, err := gemini.FileTLS(certfile, keyfile) +	if err != nil { +		log.Fatal(err) +	} + +	// set up the network listener +	listener, err := net.Listen("tcp4", ":1965") +	if err != nil { +		log.Fatal(err) +	} + +	// add stdout logging to the request handler +	handler := guslog.Requests(os.Stdout, nil)(inspectHandler) + +	// run the server +	gemini.NewServer(context.Background(), tlsconf, listener, handler).Serve() +} + +func envConfig() (string, string) { +	certfile, ok := os.LookupEnv("SERVER_CERTIFICATE") +	if !ok { +		log.Fatal("missing SERVER_CERTIFICATE environment variable") +	} + +	keyfile, ok := os.LookupEnv("SERVER_PRIVATEKEY") +	if !ok { +		log.Fatal("missing SERVER_PRIVATEKEY environment variable") +	} + +	return certfile, keyfile +} + +func inspectHandler(ctx context.Context, req *gemini.Request) *gemini.Response { +	// build and return a ```-wrapped description of the connection TLS state +	body := "```\n" + displayTLSState(req.TLSState) + "\n```" +	return gemini.Success("text/gemini", bytes.NewBufferString(body)) +} + +func displayTLSState(state *tls.ConnectionState) string { +	builder := &strings.Builder{} + +	builder.WriteString("Version:             ") +	builder.WriteString(map[uint16]string{ +		tls.VersionTLS10: "TLSv1.0", +		tls.VersionTLS11: "TLSv1.1", +		tls.VersionTLS12: "TLSv1.2", +		tls.VersionTLS13: "TLSv1.3", +		tls.VersionSSL30: "SSLv3", +	}[state.Version]) +	builder.WriteString("\n") + +	builder.WriteString(fmt.Sprintf("Handshake complete:  %t\n", state.HandshakeComplete)) +	builder.WriteString(fmt.Sprintf("Did resume:          %t\n", state.DidResume)) +	builder.WriteString(fmt.Sprintf("Cipher suite:        %x\n", state.CipherSuite)) +	builder.WriteString(fmt.Sprintf("Negotiated protocol: %q\n", state.NegotiatedProtocol)) +	builder.WriteString(fmt.Sprintf("Server name:         %s\n", state.ServerName)) + +	builder.WriteString(fmt.Sprintf("Certificates (%d)\n", len(state.PeerCertificates))) +	for i, cert := range state.PeerCertificates { +		builder.WriteString(fmt.Sprintf("  #%d:                %s\n", i+1, fingerprint(cert))) +	} + +	return builder.String() +} + +func fingerprint(cert *x509.Certificate) []byte { +	raw := md5.Sum(cert.Raw) +	dst := make([]byte, hex.EncodedLen(len(raw))) +	hex.Encode(dst, raw[:]) +	return dst +}  | 
