summaryrefslogtreecommitdiff
path: root/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'client.go')
-rw-r--r--client.go87
1 files changed, 87 insertions, 0 deletions
diff --git a/client.go b/client.go
new file mode 100644
index 0000000..3631bc5
--- /dev/null
+++ b/client.go
@@ -0,0 +1,87 @@
+package sliderule
+
+import (
+ "crypto/tls"
+ "errors"
+ "fmt"
+ neturl "net/url"
+
+ "tildegit.org/tjp/sliderule/finger"
+ "tildegit.org/tjp/sliderule/gemini"
+ "tildegit.org/tjp/sliderule/gopher"
+ "tildegit.org/tjp/sliderule/internal/types"
+ "tildegit.org/tjp/sliderule/spartan"
+)
+
+type protocolClient interface {
+ RoundTrip(*Request) (*Response, error)
+ IsRedirect(*Response) bool
+}
+
+// Client is a multi-protocol client which handles all protocols known to sliderule.
+type Client struct {
+ MaxRedirects int
+
+ protos map[string]protocolClient
+}
+
+const DefaultMaxRedirects int = 2
+
+var ExceededMaxRedirects = errors.New("Client: exceeded MaxRedirects")
+
+// NewClient builds a Client object.
+//
+// tlsConf may be nil, in which case gemini requests connections will not be made
+// with any client certificate.
+func NewClient(tlsConf *tls.Config) Client {
+ return Client{
+ protos: map[string]protocolClient{
+ "finger": finger.Client{},
+ "gopher": gopher.Client{},
+ "gemini": gemini.NewClient(tlsConf),
+ "spartan": spartan.NewClient(),
+ },
+ MaxRedirects: DefaultMaxRedirects,
+ }
+}
+
+// RoundTrip sends a single request and returns the repsonse.
+//
+// If the response is a redirect it will be returned, rather than fetched.
+func (c Client) RoundTrip(request *Request) (*Response, error) {
+ pc, ok := c.protos[request.Scheme]
+ if !ok {
+ return nil, fmt.Errorf("unrecognized protocol: %s", request.Scheme)
+ }
+ return pc.RoundTrip(request)
+}
+
+// Fetch collects a resource from a URL including following any redirects.
+func (c Client) Fetch(url string) (*Response, error) {
+ u, err := neturl.Parse(url)
+ if err != nil {
+ return nil, err
+ }
+
+ for i := 0; i <= c.MaxRedirects; i += 1 {
+ response, err := c.RoundTrip(&types.Request{URL: u})
+ if err != nil {
+ return nil, err
+ }
+
+ if !c.protos[u.Scheme].IsRedirect(response) {
+ return response, nil
+ }
+
+ prev := u
+ u, err = neturl.Parse(response.Meta.(string))
+ if err != nil {
+ return nil, err
+ }
+ if u.Scheme == "" {
+ u.Scheme = prev.Scheme
+ }
+ }
+
+ return nil, ExceededMaxRedirects
+}