aboutsummaryrefslogtreecommitdiff
path: root/server.go
diff options
context:
space:
mode:
authorYotam Nachum <me@yotam.net>2019-10-26 13:39:56 +0300
committerYotam Nachum <me@yotam.net>2019-10-26 13:39:56 +0300
commit1c7e11ca599d2cf2783639ace662b7039d71a182 (patch)
tree2fdb099fb2859d8f5af0089235b0cfa8ed474235 /server.go
downloadgo-gemini-1c7e11ca599d2cf2783639ace662b7039d71a182.tar.gz
go-gemini-1c7e11ca599d2cf2783639ace662b7039d71a182.zip
Initial commit
Diffstat (limited to 'server.go')
-rw-r--r--server.go118
1 files changed, 118 insertions, 0 deletions
diff --git a/server.go b/server.go
new file mode 100644
index 0000000..ff7df80
--- /dev/null
+++ b/server.go
@@ -0,0 +1,118 @@
+package gemini
+
+import (
+ "bufio"
+ "crypto/tls"
+ "fmt"
+ "io"
+ "net"
+ "strings"
+)
+
+// Request contains the data of the client request
+type Request struct {
+ URL string
+}
+
+// Handler is the interface a struct need to implement to be able to handle Gemini requests
+type Handler interface {
+ Handle(r Request) Response
+}
+
+// ListenAndServe create a TCP server on the specified address and pass
+// new connections to the given handler.
+// Each request is handled in a separate goroutine.
+func ListenAndServe(addr, certFile, keyFile string, handler Handler) error {
+ if addr == "" {
+ addr = "127.0.0.1:1965"
+ }
+
+ listener, err := listen(addr, certFile, keyFile)
+ if err != nil {
+ return err
+ }
+
+ err = serve(listener, handler)
+ if err != nil {
+ return err
+ }
+
+ err = listener.Close()
+ if err != nil {
+ return fmt.Errorf("failed to close the listener: %v", err)
+ }
+
+ return nil
+}
+
+func listen(addr, certFile, keyFile string) (net.Listener, error) {
+ cer, err := tls.LoadX509KeyPair(certFile, keyFile)
+ if err != nil {
+ return nil, fmt.Errorf("failed to load certificates: %v", err)
+ }
+
+ config := &tls.Config{Certificates: []tls.Certificate{cer}}
+ ln, err := tls.Listen("tcp", addr, config)
+ if err != nil {
+ return nil, fmt.Errorf("failed to listen: %v", err)
+ }
+
+ return ln, nil
+}
+
+func serve(listener net.Listener, handler Handler) error {
+ for {
+ conn, err := listener.Accept()
+ if err != nil {
+ continue
+ }
+
+ go handleConnection(conn, handler)
+ }
+}
+
+func handleConnection(conn io.ReadWriteCloser, handler Handler) {
+ defer conn.Close()
+
+ requestURL, err := getRequestURL(conn)
+ if err != nil {
+ return
+ }
+
+ request := Request{requestURL}
+ response := handler.Handle(request)
+ defer response.Body.Close()
+
+ err = writeResponse(conn, response)
+ if err != nil {
+ return
+ }
+}
+
+func getRequestURL(conn io.Reader) (string, error) {
+ scanner := bufio.NewScanner(conn)
+ if ok := scanner.Scan(); !ok {
+ return "", scanner.Err()
+ }
+
+ rawURL := scanner.Text()
+ if strings.Contains(rawURL, "://") {
+ return rawURL, nil
+ }
+
+ return fmt.Sprintf("gemini://%s", rawURL), nil
+}
+
+func writeResponse(conn io.Writer, response Response) error {
+ _, err := fmt.Fprintf(conn, "%d %s\r\n", response.Status, response.Meta)
+ if err != nil {
+ return fmt.Errorf("failed to write header line to the client: %v", err)
+ }
+
+ _, err = io.Copy(conn, response.Body)
+ if err != nil {
+ return fmt.Errorf("failed to write the response body to the client: %v", err)
+ }
+
+ return nil
+}