aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoradnano <[email protected]>2020-09-21 17:23:51 -0400
committeradnano <[email protected]>2020-09-21 17:23:51 -0400
commite20b8a0a5e8474b14b0c6702ffc1fdc7df76756e (patch)
tree84f25b510874740a2d568b5be3f160256f04743e
parentAdd LICENSE (diff)
downloadgo-gemini-e20b8a0a5e8474b14b0c6702ffc1fdc7df76756e.tar.xz
go-gemini-e20b8a0a5e8474b14b0c6702ffc1fdc7df76756e.zip
Add examples
-rw-r--r--README.md97
-rw-r--r--client.go21
-rw-r--r--example/client/client.go54
-rw-r--r--example/server/.gitignore2
-rw-r--r--example/server/server.go41
-rw-r--r--server.go25
-rw-r--r--util.go16
7 files changed, 140 insertions, 116 deletions
diff --git a/README.md b/README.md
index 7800c2f..a055361 100644
--- a/README.md
+++ b/README.md
@@ -3,98 +3,13 @@
`go-gemini` implements the [Gemini protocol](https://gemini.circumlunar.space) in
Go.
-It aims to provide an interface similar to that of `net/http` to make it easy
-to develop Gemini clients and servers.
+It aims to provide an API similar to that of `net/http` to make it easy to
+develop Gemini clients and servers.
-## Usage
+## Examples
-First generate TLS keys for your server to use.
+See `examples/client` and `examples/server` for an example client and server.
-```sh
-openssl genrsa -out server.key 2048
-openssl ecparam -genkey -name secp384r1 -out server.key
-openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
-```
+To run the examples:
-Next, import and use `go-gemini`. Here is a simple server:
-
-```go
-import (
- "git.sr.ht/~adnano/go-gemini"
-)
-
-func main() {
- config := &tls.Config{}
- cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
- if err != nil {
- log.Fatal(err)
- }
- config.Certificates = append(config.Certificates, cert)
-
- mux := &gemini.Mux{}
- mux.HandleFunc("/", func(url *url.URL) *gemini.Response {
- return &gemini.Response{
- Status: gemini.StatusSuccess,
- Meta: "text/gemini",
- Body: []byte("You requested " + url.String()),
- }
- })
-
- server := gemini.Server{
- TLSConfig: config,
- Handler: mux,
- }
- server.ListenAndServe()
-}
-```
-
-And a simple client:
-
-```go
-import (
- "git.sr.ht/~adnano/go-gemini"
-)
-
-var client gemini.Client
-
-func makeRequest(url string) {
- resp, err := client.Get(url)
- if err != nil {
- log.Fatal(err)
- }
-
- fmt.Println("Status code:", resp.Status)
- fmt.Println("Meta:", resp.Meta)
-
- switch resp.Status / 10 {
- case gemini.StatusClassInput:
- scanner := bufio.NewScanner(os.Stdin)
- fmt.Printf("%s: ", resp.Meta)
- scanner.Scan()
- query := scanner.Text()
- makeRequest(url + "?" + query)
- return
- case gemini.StatusClassSuccess:
- fmt.Print("Body:\n", string(resp.Body))
- case gemini.StatusClassRedirect:
- log.Print("Redirecting to ", resp.Meta)
- makeRequest(resp.Meta)
- return
- case gemini.StatusClassTemporaryFailure:
- log.Fatal("Temporary failure")
- case gemini.StatusClassPermanentFailure:
- log.Fatal("Permanent failure")
- case gemini.StatusClassClientCertificateRequired:
- log.Fatal("Client certificate required")
- default:
- log.Fatal("Protocol error: invalid status code")
- }
-}
-
-func main() {
- if len(os.Args) < 2 {
- log.Fatalf("usage: %s gemini://...", os.Args[0])
- }
- makeRequest(os.Args[1])
-}
-```
+ go run -tags=example ./example/server
diff --git a/client.go b/client.go
index b44c8fb..9609ac5 100644
--- a/client.go
+++ b/client.go
@@ -1,7 +1,6 @@
package gemini
import (
- "bufio"
"crypto/tls"
"errors"
"io/ioutil"
@@ -11,15 +10,16 @@ import (
)
var (
- ProtocolError = errors.New("Protocol error")
+ ErrProtocol = errors.New("Protocol error")
)
// Client is a Gemini client.
type Client struct {
- TLSConfig tls.Config
+ TLSConfig *tls.Config // TODO: Client certificate support
}
-func (c *Client) Get(url string) (*Response, error) {
+// Request makes a request for the provided URL. The host is inferred from the URL.
+func (c *Client) Request(url string) (*Response, error) {
req, err := NewRequest(url)
if err != nil {
return nil, err
@@ -27,7 +27,8 @@ func (c *Client) Get(url string) (*Response, error) {
return c.Do(req)
}
-func (c *Client) GetProxy(host, url string) (*Response, error) {
+// ProxyRequest requests the provided URL from the provided host.
+func (c *Client) ProxyRequest(host, url string) (*Response, error) {
req, err := NewProxyRequest(host, url)
if err != nil {
return nil, err
@@ -73,6 +74,7 @@ func NewProxyRequest(host, rawurl string) (*Request, error) {
}, nil
}
+// Do completes a request.
func (c *Client) Do(req *Request) (*Response, error) {
host := req.Host
if strings.LastIndex(host, ":") == -1 {
@@ -113,13 +115,14 @@ func (c *Client) Do(req *Request) (*Response, error) {
return nil, err
}
if space[0] != ' ' {
- return nil, ProtocolError
+ return nil, ErrProtocol
}
// Read the meta
- scanner := bufio.NewScanner(conn)
- scanner.Scan()
- meta := scanner.Text()
+ meta, err := readLine(conn)
+ if err != nil {
+ return nil, err
+ }
// Read the response body
body, err := ioutil.ReadAll(conn)
diff --git a/example/client/client.go b/example/client/client.go
new file mode 100644
index 0000000..5b6fde2
--- /dev/null
+++ b/example/client/client.go
@@ -0,0 +1,54 @@
+// +build example
+
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "git.sr.ht/~adnano/go-gemini"
+ "log"
+ "os"
+)
+
+var client gemini.Client
+
+func makeRequest(url string) {
+ resp, err := client.Request(url)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println("Status code:", resp.Status)
+ fmt.Println("Meta:", resp.Meta)
+
+ switch resp.Status / 10 {
+ case gemini.StatusClassInput:
+ scanner := bufio.NewScanner(os.Stdin)
+ fmt.Printf("%s: ", resp.Meta)
+ scanner.Scan()
+ query := scanner.Text()
+ makeRequest(url + "?" + query)
+ return
+ case gemini.StatusClassSuccess:
+ fmt.Print("Body:\n", string(resp.Body))
+ case gemini.StatusClassRedirect:
+ log.Print("Redirecting to ", resp.Meta)
+ makeRequest(resp.Meta)
+ return
+ case gemini.StatusClassTemporaryFailure:
+ log.Fatal("Temporary failure")
+ case gemini.StatusClassPermanentFailure:
+ log.Fatal("Permanent failure")
+ case gemini.StatusClassClientCertificateRequired:
+ log.Fatal("Client Certificate Required")
+ default:
+ log.Fatal("Protocol Error")
+ }
+}
+
+func main() {
+ if len(os.Args) < 2 {
+ log.Fatalf("usage: %s gemini://...", os.Args[0])
+ }
+ makeRequest(os.Args[1])
+}
diff --git a/example/server/.gitignore b/example/server/.gitignore
new file mode 100644
index 0000000..10cdeb2
--- /dev/null
+++ b/example/server/.gitignore
@@ -0,0 +1,2 @@
+server.crt
+server.key
diff --git a/example/server/server.go b/example/server/server.go
new file mode 100644
index 0000000..3f0a3e3
--- /dev/null
+++ b/example/server/server.go
@@ -0,0 +1,41 @@
+// +build example
+
+package main
+
+import (
+ "crypto/tls"
+ "git.sr.ht/~adnano/go-gemini"
+ "log"
+ "net/url"
+)
+
+func main() {
+ // Load a TLS key pair.
+ // To generate a TLS key pair, run:
+ //
+ // openssl genrsa -out server.key 2048
+ // openssl ecparam -genkey -name secp384r1 -out server.key
+ // openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
+ //
+ config := &tls.Config{}
+ cert, err := tls.LoadX509KeyPair("example/server/server.crt", "example/server/server.key")
+ if err != nil {
+ log.Fatal(err)
+ }
+ config.Certificates = append(config.Certificates, cert)
+
+ mux := &gemini.Mux{}
+ mux.HandleFunc("/", func(url *url.URL) *gemini.Response {
+ return &gemini.Response{
+ Status: gemini.StatusSuccess,
+ Meta: "text/gemini",
+ Body: []byte("You requested " + url.String()),
+ }
+ })
+
+ server := gemini.Server{
+ TLSConfig: config,
+ Handler: mux,
+ }
+ server.ListenAndServe()
+}
diff --git a/server.go b/server.go
index 24c6a15..785add3 100644
--- a/server.go
+++ b/server.go
@@ -1,7 +1,6 @@
package gemini
import (
- "bufio"
"crypto/tls"
"io"
"net"
@@ -49,12 +48,12 @@ type Response struct {
Body []byte
}
-// Write writes the Response to the provided io.Writer.
+// Write writes the Gemini response header and body to the provided io.Writer.
func (r *Response) Write(w io.Writer) {
header := strconv.Itoa(r.Status) + " " + r.Meta + "\r\n"
w.Write([]byte(header))
- // Only write response body on success
+ // Only write the response body on success
if r.Status/10 == StatusClassSuccess {
w.Write(r.Body)
}
@@ -67,7 +66,7 @@ type Server struct {
Handler Handler
}
-// ListenAndServer listens on the given address and serves.
+// ListenAndServe listens for requests at the server's configured address.
func (s *Server) ListenAndServe() error {
addr := s.Addr
if addr == "" {
@@ -108,8 +107,8 @@ func (s *Server) Serve(ln net.Listener) error {
// Handler handles a url with a response.
type Handler interface {
- // Serve accepts a url, as that is the only information that the client
- // provides in a request.
+ // Serve accepts a url, as that is the only information that is provided in
+ // a Gemini request.
Serve(*url.URL) *Response
}
@@ -139,6 +138,7 @@ func (m *Mux) match(url *url.URL) Handler {
return nil
}
+// Handle registers a handler for the given pattern.
func (m *Mux) Handle(pattern string, handler Handler) {
url, err := url.Parse(pattern)
if err != nil {
@@ -152,11 +152,13 @@ func (m *Mux) Handle(pattern string, handler Handler) {
})
}
+// HandleFunc registers a HandlerFunc for the given pattern.
func (m *Mux) HandleFunc(pattern string, handlerFunc func(url *url.URL) *Response) {
handler := HandlerFunc(handlerFunc)
m.Handle(pattern, handler)
}
+// Serve responds to the request with the appropriate handler.
func (m *Mux) Serve(url *url.URL) *Response {
h := m.match(url)
if h == nil {
@@ -168,18 +170,9 @@ func (m *Mux) Serve(url *url.URL) *Response {
return h.Serve(url)
}
+// A wrapper around a bare function that implements Handler.
type HandlerFunc func(url *url.URL) *Response
func (f HandlerFunc) Serve(url *url.URL) *Response {
return f(url)
}
-
-// readLine reads a line.
-func readLine(r io.Reader) (string, error) {
- scanner := bufio.NewScanner(r)
- scanner.Scan()
- if err := scanner.Err(); err != nil {
- return "", err
- }
- return scanner.Text(), nil
-}
diff --git a/util.go b/util.go
new file mode 100644
index 0000000..efaa9c8
--- /dev/null
+++ b/util.go
@@ -0,0 +1,16 @@
+package gemini
+
+import (
+ "bufio"
+ "io"
+)
+
+// readLine reads a line.
+func readLine(r io.Reader) (string, error) {
+ scanner := bufio.NewScanner(r)
+ scanner.Scan()
+ if err := scanner.Err(); err != nil {
+ return "", err
+ }
+ return scanner.Text(), nil
+}