-
Notifications
You must be signed in to change notification settings - Fork 10
/
server.go
149 lines (121 loc) · 3.36 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package gemini
import (
"bufio"
"crypto/tls"
"fmt"
"io"
"net"
"net/url"
"strings"
)
// Request contains the data of the client request
type Request struct {
URL *url.URL
}
// 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 BadRequest (59) if there was URL parsing error
writeResponse(conn, &Response{Status: StatusBadRequest, Meta: "Bad URL: " + err.Error()})
return
}
request := Request{requestURL}
response := handler.Handle(request)
if response.Body != nil {
defer response.Body.Close()
}
err = writeResponse(conn, response)
if err != nil {
return
}
}
func getRequestURL(conn io.Reader) (*url.URL, error) {
scanner := bufio.NewScanner(conn)
if ok := scanner.Scan(); !ok {
return nil, scanner.Err()
}
rawURL := strings.TrimSuffix(scanner.Text(), "\r\n")
parsedURL, err := url.Parse(rawURL)
if err != nil {
return nil, fmt.Errorf("couldn't parse request URL")
}
if parsedURL.Scheme == "" {
// Default scheme is gemini
parsedURL.Scheme = "gemini"
}
return parsedURL, 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)
}
if response.Body == nil {
return nil
}
_, 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
}
// ErrorResponse create a response from the given error with the error string as the Meta field.
// If the error is of type gemini.Error, the status will be taken from the status field,
// otherwise it will default to StatusTemporaryFailure.
// If the error is nil, the function will panic.
func ErrorResponse(err error) *Response {
if err == nil {
panic("nil error is not a valid parameter")
}
if ge, ok := err.(Error); ok {
return &Response{Status: ge.Status, Meta: ge.Error(), Body: nil}
}
return &Response{Status: StatusTemporaryFailure, Meta: err.Error(), Body: nil}
}