Add (*Client).Get function
This commit is contained in:
parent
12a9deb1a6
commit
239ec885f7
25
client.go
25
client.go
@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Client represents a Gemini client.
|
||||
@ -28,8 +29,17 @@ type Client struct {
|
||||
TrustCertificate func(hostname string, cert *x509.Certificate, knownHosts *KnownHosts) error
|
||||
}
|
||||
|
||||
// Send sends a Gemini request and returns a Gemini response.
|
||||
func (c *Client) Send(req *Request) (*Response, error) {
|
||||
// Get performs a Gemini request for the given url.
|
||||
func (c *Client) Get(url string) (*Response, error) {
|
||||
req, err := NewRequest(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// Do performs a Gemini request and returns a Gemini response.
|
||||
func (c *Client) Do(req *Request) (*Response, error) {
|
||||
// Connect to the host
|
||||
config := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
@ -92,9 +102,18 @@ func (c *Client) Send(req *Request) (*Response, error) {
|
||||
if c.GetCertificate != nil {
|
||||
if cert := c.GetCertificate(hostname(req.Host), &c.CertificateStore); cert != nil {
|
||||
req.Certificate = cert
|
||||
return c.Send(req)
|
||||
return c.Do(req)
|
||||
}
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// hostname returns the host without the port.
|
||||
func hostname(host string) string {
|
||||
hostname, _, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return host
|
||||
}
|
||||
return hostname
|
||||
}
|
||||
|
24
doc.go
24
doc.go
@ -1,26 +1,34 @@
|
||||
/*
|
||||
Package gemini implements the Gemini protocol.
|
||||
|
||||
Send makes a Gemini request with the default client:
|
||||
Get makes a Gemini request:
|
||||
|
||||
req := gemini.NewRequest("gemini://example.com")
|
||||
resp, err := gemini.Send(req)
|
||||
resp, err := gemini.Get("gemini://example.com")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
// ...
|
||||
|
||||
For control over client behavior, create a custom Client:
|
||||
The client must close the response body when finished with it:
|
||||
|
||||
resp, err := gemini.Get("gemini://example.com")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
// ...
|
||||
|
||||
For control over client behavior, create a Client:
|
||||
|
||||
var client gemini.Client
|
||||
resp, err := client.Send(req)
|
||||
resp, err := client.Get("gemini://example.com")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
// ...
|
||||
|
||||
The default client loads known hosts from "$XDG_DATA_HOME/gemini/known_hosts".
|
||||
Custom clients can load their own list of known hosts:
|
||||
Clients can load their own list of known hosts:
|
||||
|
||||
err := client.KnownHosts.Load("path/to/my/known_hosts")
|
||||
if err != nil {
|
||||
@ -33,7 +41,7 @@ Clients can control when to trust certificates with TrustCertificate:
|
||||
return knownHosts.Lookup(hostname, cert)
|
||||
}
|
||||
|
||||
If a server responds with StatusCertificateRequired, the default client will generate a certificate and resend the request with it. Custom clients can do so in GetCertificate:
|
||||
Clients can control what to do when a server requests a certificate:
|
||||
|
||||
client.GetCertificate = func(hostname string, store *gemini.CertificateStore) *tls.Certificate {
|
||||
// If the certificate is in the store, return it
|
||||
|
@ -68,7 +68,7 @@ func init() {
|
||||
|
||||
// sendRequest sends a request to the given URL.
|
||||
func sendRequest(req *gmi.Request) error {
|
||||
resp, err := client.Send(req)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -149,19 +149,8 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var host string
|
||||
if len(os.Args) >= 3 {
|
||||
host = os.Args[2]
|
||||
}
|
||||
|
||||
url := os.Args[1]
|
||||
var req *gmi.Request
|
||||
var err error
|
||||
if host != "" {
|
||||
req, err = gmi.NewRequestTo(url, host)
|
||||
} else {
|
||||
req, err = gmi.NewRequest(url)
|
||||
}
|
||||
req, err := gmi.NewRequest(url)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
119
gemini.go
119
gemini.go
@ -8,82 +8,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Status codes.
|
||||
type Status int
|
||||
|
||||
const (
|
||||
StatusInput Status = 10
|
||||
StatusSensitiveInput Status = 11
|
||||
StatusSuccess Status = 20
|
||||
StatusRedirect Status = 30
|
||||
StatusRedirectPermanent Status = 31
|
||||
StatusTemporaryFailure Status = 40
|
||||
StatusServerUnavailable Status = 41
|
||||
StatusCGIError Status = 42
|
||||
StatusProxyError Status = 43
|
||||
StatusSlowDown Status = 44
|
||||
StatusPermanentFailure Status = 50
|
||||
StatusNotFound Status = 51
|
||||
StatusGone Status = 52
|
||||
StatusProxyRequestRefused Status = 53
|
||||
StatusBadRequest Status = 59
|
||||
StatusCertificateRequired Status = 60
|
||||
StatusCertificateNotAuthorized Status = 61
|
||||
StatusCertificateNotValid Status = 62
|
||||
)
|
||||
|
||||
// Class returns the status class for this status code.
|
||||
func (s Status) Class() StatusClass {
|
||||
return StatusClass(s / 10)
|
||||
}
|
||||
|
||||
// StatusMessage returns the status message corresponding to the provided
|
||||
// status code.
|
||||
// StatusMessage returns an empty string for input, successs, and redirect
|
||||
// status codes.
|
||||
func (s Status) Message() string {
|
||||
switch s {
|
||||
case StatusTemporaryFailure:
|
||||
return "TemporaryFailure"
|
||||
case StatusServerUnavailable:
|
||||
return "Server unavailable"
|
||||
case StatusCGIError:
|
||||
return "CGI error"
|
||||
case StatusProxyError:
|
||||
return "Proxy error"
|
||||
case StatusSlowDown:
|
||||
return "Slow down"
|
||||
case StatusPermanentFailure:
|
||||
return "PermanentFailure"
|
||||
case StatusNotFound:
|
||||
return "Not found"
|
||||
case StatusGone:
|
||||
return "Gone"
|
||||
case StatusProxyRequestRefused:
|
||||
return "Proxy request refused"
|
||||
case StatusBadRequest:
|
||||
return "Bad request"
|
||||
case StatusCertificateRequired:
|
||||
return "Certificate required"
|
||||
case StatusCertificateNotAuthorized:
|
||||
return "Certificate not authorized"
|
||||
case StatusCertificateNotValid:
|
||||
return "Certificate not valid"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Status code categories.
|
||||
type StatusClass int
|
||||
|
||||
const (
|
||||
StatusClassInput StatusClass = 1
|
||||
StatusClassSuccess StatusClass = 2
|
||||
StatusClassRedirect StatusClass = 3
|
||||
StatusClassTemporaryFailure StatusClass = 4
|
||||
StatusClassPermanentFailure StatusClass = 5
|
||||
StatusClassCertificateRequired StatusClass = 6
|
||||
)
|
||||
var crlf = []byte("\r\n")
|
||||
|
||||
// Errors.
|
||||
var (
|
||||
@ -96,48 +21,42 @@ var (
|
||||
ErrBodyNotAllowed = errors.New("gemini: response status code does not allow for body")
|
||||
)
|
||||
|
||||
// DefaultClient is the default client. It is used by Send.
|
||||
// DefaultClient is the default client. It is used by Get and Do.
|
||||
//
|
||||
// On the first request, DefaultClient will load the default list of known hosts.
|
||||
// On the first request, DefaultClient loads the default list of known hosts.
|
||||
var DefaultClient Client
|
||||
|
||||
var (
|
||||
crlf = []byte("\r\n")
|
||||
)
|
||||
// Get performs a Gemini request for the given url.
|
||||
//
|
||||
// Get is a wrapper around DefaultClient.Get.
|
||||
func Get(url string) (*Response, error) {
|
||||
return DefaultClient.Get(url)
|
||||
}
|
||||
|
||||
// Do performs a Gemini request and returns a Gemini response.
|
||||
//
|
||||
// Do is a wrapper around DefaultClient.Do.
|
||||
func Do(req *Request) (*Response, error) {
|
||||
return DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
var defaultClientOnce sync.Once
|
||||
|
||||
func init() {
|
||||
DefaultClient.TrustCertificate = func(hostname string, cert *x509.Certificate, knownHosts *KnownHosts) error {
|
||||
// Load the hosts only once. This is so that the hosts don't have to be loaded
|
||||
// for those using their own clients.
|
||||
setupDefaultClientOnce.Do(setupDefaultClient)
|
||||
defaultClientOnce.Do(func() { knownHosts.LoadDefault() })
|
||||
return knownHosts.Lookup(hostname, cert)
|
||||
}
|
||||
DefaultClient.GetCertificate = func(hostname string, store *CertificateStore) *tls.Certificate {
|
||||
// If the certificate is in the store, return it
|
||||
if cert, err := store.Lookup(hostname); err == nil {
|
||||
return cert
|
||||
}
|
||||
// Otherwise, generate a certificate
|
||||
duration := time.Hour
|
||||
cert, err := NewCertificate(hostname, duration)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// Store and return the certificate
|
||||
store.Add(hostname, cert)
|
||||
return &cert
|
||||
}
|
||||
}
|
||||
|
||||
var setupDefaultClientOnce sync.Once
|
||||
|
||||
func setupDefaultClient() {
|
||||
DefaultClient.KnownHosts.LoadDefault()
|
||||
}
|
||||
|
||||
// Send sends a Gemini request and returns a Gemini response.
|
||||
//
|
||||
// Send is a wrapper around DefaultClient.Send.
|
||||
func Send(req *Request) (*Response, error) {
|
||||
return DefaultClient.Send(req)
|
||||
}
|
||||
|
25
request.go
25
request.go
@ -33,15 +33,6 @@ type Request struct {
|
||||
TLS tls.ConnectionState
|
||||
}
|
||||
|
||||
// hostname returns the host without the port.
|
||||
func hostname(host string) string {
|
||||
hostname, _, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return host
|
||||
}
|
||||
return hostname
|
||||
}
|
||||
|
||||
// NewRequest returns a new request. The host is inferred from the URL.
|
||||
func NewRequest(rawurl string) (*Request, error) {
|
||||
u, err := url.Parse(rawurl)
|
||||
@ -54,29 +45,13 @@ func NewRequest(rawurl string) (*Request, error) {
|
||||
// NewRequestFromURL returns a new request for the given URL.
|
||||
// The host is inferred from the URL.
|
||||
func NewRequestFromURL(url *url.URL) (*Request, error) {
|
||||
// If there is no port, use the default port of 1965
|
||||
host := url.Host
|
||||
if url.Port() == "" {
|
||||
host += ":1965"
|
||||
}
|
||||
|
||||
return &Request{
|
||||
Host: host,
|
||||
URL: url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewRequestTo returns a new request for the provided URL to the provided host.
|
||||
// The host must contain a port.
|
||||
func NewRequestTo(rawurl, host string) (*Request, error) {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Request{
|
||||
Host: host,
|
||||
URL: u,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
78
status.go
Normal file
78
status.go
Normal file
@ -0,0 +1,78 @@
|
||||
package gemini
|
||||
|
||||
// Status codes.
|
||||
type Status int
|
||||
|
||||
const (
|
||||
StatusInput Status = 10
|
||||
StatusSensitiveInput Status = 11
|
||||
StatusSuccess Status = 20
|
||||
StatusRedirect Status = 30
|
||||
StatusRedirectPermanent Status = 31
|
||||
StatusTemporaryFailure Status = 40
|
||||
StatusServerUnavailable Status = 41
|
||||
StatusCGIError Status = 42
|
||||
StatusProxyError Status = 43
|
||||
StatusSlowDown Status = 44
|
||||
StatusPermanentFailure Status = 50
|
||||
StatusNotFound Status = 51
|
||||
StatusGone Status = 52
|
||||
StatusProxyRequestRefused Status = 53
|
||||
StatusBadRequest Status = 59
|
||||
StatusCertificateRequired Status = 60
|
||||
StatusCertificateNotAuthorized Status = 61
|
||||
StatusCertificateNotValid Status = 62
|
||||
)
|
||||
|
||||
// Class returns the status class for this status code.
|
||||
func (s Status) Class() StatusClass {
|
||||
return StatusClass(s / 10)
|
||||
}
|
||||
|
||||
// StatusMessage returns the status message corresponding to the provided
|
||||
// status code.
|
||||
// StatusMessage returns an empty string for input, successs, and redirect
|
||||
// status codes.
|
||||
func (s Status) Message() string {
|
||||
switch s {
|
||||
case StatusTemporaryFailure:
|
||||
return "TemporaryFailure"
|
||||
case StatusServerUnavailable:
|
||||
return "Server unavailable"
|
||||
case StatusCGIError:
|
||||
return "CGI error"
|
||||
case StatusProxyError:
|
||||
return "Proxy error"
|
||||
case StatusSlowDown:
|
||||
return "Slow down"
|
||||
case StatusPermanentFailure:
|
||||
return "PermanentFailure"
|
||||
case StatusNotFound:
|
||||
return "Not found"
|
||||
case StatusGone:
|
||||
return "Gone"
|
||||
case StatusProxyRequestRefused:
|
||||
return "Proxy request refused"
|
||||
case StatusBadRequest:
|
||||
return "Bad request"
|
||||
case StatusCertificateRequired:
|
||||
return "Certificate required"
|
||||
case StatusCertificateNotAuthorized:
|
||||
return "Certificate not authorized"
|
||||
case StatusCertificateNotValid:
|
||||
return "Certificate not valid"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Status code categories.
|
||||
type StatusClass int
|
||||
|
||||
const (
|
||||
StatusClassInput StatusClass = 1
|
||||
StatusClassSuccess StatusClass = 2
|
||||
StatusClassRedirect StatusClass = 3
|
||||
StatusClassTemporaryFailure StatusClass = 4
|
||||
StatusClassPermanentFailure StatusClass = 5
|
||||
StatusClassCertificateRequired StatusClass = 6
|
||||
)
|
Loading…
Reference in New Issue
Block a user