From 23bc5f4fb7542e64c94eaa7fe2c7a6aa55010898 Mon Sep 17 00:00:00 2001 From: tjpcc Date: Sat, 12 Aug 2023 09:40:39 -0600 Subject: move common types to an internal package This helps avoid import cycles. --- finger/client.go | 8 ++-- finger/request.go | 6 +-- finger/response.go | 10 ++--- finger/serve.go | 8 ++-- finger/system.go | 6 +-- gemini/client.go | 8 ++-- gemini/request.go | 8 ++-- gemini/response.go | 100 ++++++++++++++++++++++----------------------- gemini/response_test.go | 18 ++++---- gemini/roundtrip_test.go | 8 ++-- gemini/serve.go | 16 ++++---- gopher/client.go | 10 ++--- gopher/request.go | 6 +-- gopher/response.go | 70 +++++++++++++++---------------- gopher/serve.go | 10 ++--- handler.go | 28 ++++--------- internal/types/handler.go | 28 +++++++++++++ internal/types/request.go | 49 ++++++++++++++++++++++ internal/types/response.go | 35 ++++++++++++++++ internal/types/server.go | 42 +++++++++++++++++++ logging/middleware.go | 16 ++++---- request.go | 48 +--------------------- response.go | 36 ++-------------- server.go | 41 +------------------ spartan/client.go | 8 ++-- spartan/request.go | 12 +++--- spartan/response.go | 36 ++++++++-------- spartan/serve.go | 10 ++--- 28 files changed, 356 insertions(+), 325 deletions(-) create mode 100644 internal/types/handler.go create mode 100644 internal/types/request.go create mode 100644 internal/types/response.go create mode 100644 internal/types/server.go diff --git a/finger/client.go b/finger/client.go index 8ebf3ca..75a382f 100644 --- a/finger/client.go +++ b/finger/client.go @@ -7,7 +7,7 @@ import ( "net" "strings" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // Client is used for sending finger requests and reading responses. @@ -18,7 +18,7 @@ import ( type Client struct{} // RoundTrip sends a single finger request and returns its response. -func (c Client) RoundTrip(request *sr.Request) (*sr.Response, error) { +func (c Client) RoundTrip(request *types.Request) (*types.Response, error) { if request.Scheme != "finger" && request.Scheme != "" { return nil, errors.New("non-finger protocols not supported") } @@ -46,11 +46,11 @@ func (c Client) RoundTrip(request *sr.Request) (*sr.Response, error) { return nil, err } - return &sr.Response{Body: bytes.NewBuffer(response)}, nil + return &types.Response{Body: bytes.NewBuffer(response)}, nil } // Fetch resolves a finger query. -func (c Client) Fetch(query string) (*sr.Response, error) { +func (c Client) Fetch(query string) (*types.Response, error) { req, err := ParseRequest(bytes.NewBufferString(query + "\r\n")) if err != nil { return nil, err diff --git a/finger/request.go b/finger/request.go index a18ff7e..b0ef102 100644 --- a/finger/request.go +++ b/finger/request.go @@ -7,7 +7,7 @@ import ( "net/url" "strings" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // ForwardingDenied is returned in response to requests for forwarding service. @@ -47,7 +47,7 @@ var InvalidFingerQuery = errors.New("Invalid finger query.") // In accordance with the recommendation of RFC 1288 section 3.2.1 // (https://datatracker.ietf.org/doc/html/rfc1288#section-3.2.1), any queries which // include a jump-host (user@host1@host2) are rejected with the ForwardingDenied error. -func ParseRequest(rdr io.Reader) (*sr.Request, error) { +func ParseRequest(rdr io.Reader) (*types.Request, error) { line, err := bufio.NewReader(rdr).ReadString('\n') if err != nil { return nil, err @@ -66,7 +66,7 @@ func ParseRequest(rdr io.Reader) (*sr.Request, error) { return nil, ForwardingDenied } - return &sr.Request{URL: &url.URL{ + return &types.Request{URL: &url.URL{ Scheme: "finger", Host: hostname, Path: "/" + username, diff --git a/finger/response.go b/finger/response.go index 8612f45..db2dfb0 100644 --- a/finger/response.go +++ b/finger/response.go @@ -5,18 +5,18 @@ import ( "io" "strings" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // Error produces a finger Response containing the error message and Status 1. -func Error(msg string) *sr.Response { +func Error(msg string) *types.Response { if !strings.HasSuffix(msg, "\r\n") { msg += "\r\n" } - return &sr.Response{Body: bytes.NewBufferString(msg), Status: 1} + return &types.Response{Body: bytes.NewBufferString(msg), Status: 1} } // Success produces a finger response with a Status of 0. -func Success(body io.Reader) *sr.Response { - return &sr.Response{Body: body} +func Success(body io.Reader) *types.Response { + return &types.Response{Body: body} } diff --git a/finger/serve.go b/finger/serve.go index 6456763..ff842e1 100644 --- a/finger/serve.go +++ b/finger/serve.go @@ -6,14 +6,14 @@ import ( "io" "net" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" "tildegit.org/tjp/sliderule/internal" "tildegit.org/tjp/sliderule/logging" ) type fingerServer struct { internal.Server - handler sr.Handler + handler types.Handler } func (fs fingerServer) Protocol() string { return "FINGER" } @@ -24,9 +24,9 @@ func NewServer( hostname string, network string, address string, - handler sr.Handler, + handler types.Handler, errLog logging.Logger, -) (sr.Server, error) { +) (types.Server, error) { fs := &fingerServer{handler: handler} hostname = internal.JoinDefaultPort(hostname, "79") diff --git a/finger/system.go b/finger/system.go index aa2cc84..30ee1e8 100644 --- a/finger/system.go +++ b/finger/system.go @@ -6,15 +6,15 @@ import ( "errors" "os/exec" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // ListingDenied is returned to reject online user listing requests. var ListingDenied = errors.New("Finger online user list denied.") // SystemFinger handles finger requests by invoking the finger(1) command-line utility. -func SystemFinger(allowListings bool) sr.Handler { - return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { +func SystemFinger(allowListings bool) types.Handler { + return types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response { fingerPath, err := exec.LookPath("finger") if err != nil { _ = request.Server.LogError( diff --git a/gemini/client.go b/gemini/client.go index e81e64a..34d5839 100644 --- a/gemini/client.go +++ b/gemini/client.go @@ -8,7 +8,7 @@ import ( "net" neturl "net/url" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // Client is used for sending gemini requests and parsing gemini responses. @@ -43,7 +43,7 @@ var ExceededMaxRedirects = errors.New("gemini.Client: exceeded MaxRedirects") // // This method will not automatically follow redirects or cache permanent failures or // redirects. -func (client Client) RoundTrip(request *sr.Request) (*sr.Response, error) { +func (client Client) RoundTrip(request *types.Request) (*types.Response, error) { if request.Scheme != "gemini" && request.Scheme != "" { return nil, errors.New("non-gemini protocols not supported") } @@ -91,14 +91,14 @@ func (client Client) RoundTrip(request *sr.Request) (*sr.Response, error) { // Fetch parses a URL string and fetches the gemini resource. // // It will resolve any redirects along the way, up to client.MaxRedirects. -func (c Client) Fetch(url string) (*sr.Response, error) { +func (c Client) Fetch(url string) (*types.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(&sr.Request{URL: u}) + response, err := c.RoundTrip(&types.Request{URL: u}) if err != nil { return nil, err } diff --git a/gemini/request.go b/gemini/request.go index 51aaae5..4eb7cf0 100644 --- a/gemini/request.go +++ b/gemini/request.go @@ -6,7 +6,7 @@ import ( "io" "net/url" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // InvalidRequestLineEnding indicates that a gemini request didn't end with "\r\n". @@ -15,7 +15,7 @@ var InvalidRequestLineEnding = errors.New("invalid request line ending") // ParseRequest parses a single gemini request from a reader. // // If the reader argument is a *bufio.Reader, it will only read a single line from it. -func ParseRequest(rdr io.Reader) (*sr.Request, error) { +func ParseRequest(rdr io.Reader) (*types.Request, error) { bufrdr, ok := rdr.(*bufio.Reader) if !ok { bufrdr = bufio.NewReader(rdr) @@ -39,14 +39,14 @@ func ParseRequest(rdr io.Reader) (*sr.Request, error) { u.Scheme = "gemini" } - return &sr.Request{URL: u}, nil + return &types.Request{URL: u}, nil } // GetTitanRequestBody fetches the request body from a titan request. // // It returns nil if the argument is not a titan request or it otherwise // does not have a request body set. -func GetTitanRequestBody(request *sr.Request) io.Reader { +func GetTitanRequestBody(request *types.Request) io.Reader { if request.Scheme != "titan" { return nil } diff --git a/gemini/response.go b/gemini/response.go index 9d4ede1..13f493a 100644 --- a/gemini/response.go +++ b/gemini/response.go @@ -8,7 +8,7 @@ import ( "strconv" "sync" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // ResponseCategory represents the various types of gemini responses. @@ -43,32 +43,32 @@ const ( ResponseCategoryCertificateRequired ) -func ResponseCategoryForStatus(status sr.Status) ResponseCategory { +func ResponseCategoryForStatus(status types.Status) ResponseCategory { return ResponseCategory(status / 10) } const ( // StatusInput indicates a required query parameter at the requested URL. - StatusInput sr.Status = sr.Status(ResponseCategoryInput) + iota + StatusInput types.Status = types.Status(ResponseCategoryInput) + iota // StatusSensitiveInput indicates a sensitive query parameter is required. StatusSensitiveInput ) const ( // StatusSuccess is a successful response. - StatusSuccess = sr.Status(ResponseCategorySuccess) + iota + StatusSuccess = types.Status(ResponseCategorySuccess) + iota ) const ( // StatusTemporaryRedirect indicates a temporary redirect to another URL. - StatusTemporaryRedirect = sr.Status(ResponseCategoryRedirect) + iota + StatusTemporaryRedirect = types.Status(ResponseCategoryRedirect) + iota // StatusPermanentRedirect indicates that the resource should always be requested at the new URL. StatusPermanentRedirect ) const ( // StatusTemporaryFailure indicates that the request failed and there is no response body. - StatusTemporaryFailure = sr.Status(ResponseCategoryTemporaryFailure) + iota + StatusTemporaryFailure = types.Status(ResponseCategoryTemporaryFailure) + iota // StatusServerUnavailable occurs when the server is unavailable due to overload or maintenance. StatusServerUnavailable // StatusCGIError is the result of a failure of a CGI script. @@ -84,7 +84,7 @@ const ( const ( // StatusPermanentFailure is a server failure which should be expected to continue indefinitely. - StatusPermanentFailure = sr.Status(ResponseCategoryPermanentFailure) + iota + StatusPermanentFailure = types.Status(ResponseCategoryPermanentFailure) + iota // StatusNotFound means the resource doesn't exist but it may in the future. StatusNotFound // StatusGone occurs when a resource will not be available any longer. @@ -92,12 +92,12 @@ const ( // StatusProxyRequestRefused means the server is unwilling to act as a proxy for the resource. StatusProxyRequestRefused // StatusBadRequest indicates that the request was malformed somehow. - StatusBadRequest = sr.Status(ResponseCategoryPermanentFailure) + 9 + StatusBadRequest = types.Status(ResponseCategoryPermanentFailure) + 9 ) const ( // StatusClientCertificateRequired is returned when a certificate was required but not provided. - StatusClientCertificateRequired = sr.Status(ResponseCategoryCertificateRequired) + iota + StatusClientCertificateRequired = types.Status(ResponseCategoryCertificateRequired) + iota // StatusCertificateNotAuthorized means the certificate doesn't grant access to the requested resource. StatusCertificateNotAuthorized // StatusCertificateNotValid means the provided client certificate is invalid. @@ -105,24 +105,24 @@ const ( ) // Input builds an input-prompting response. -func Input(prompt string) *sr.Response { - return &sr.Response{ +func Input(prompt string) *types.Response { + return &types.Response{ Status: StatusInput, Meta: prompt, } } // SensitiveInput builds a password-prompting response. -func SensitiveInput(prompt string) *sr.Response { - return &sr.Response{ +func SensitiveInput(prompt string) *types.Response { + return &types.Response{ Status: StatusSensitiveInput, Meta: prompt, } } // Success builds a success response with resource body. -func Success(mediatype string, body io.Reader) *sr.Response { - return &sr.Response{ +func Success(mediatype string, body io.Reader) *types.Response { + return &types.Response{ Status: StatusSuccess, Meta: mediatype, Body: body, @@ -130,120 +130,120 @@ func Success(mediatype string, body io.Reader) *sr.Response { } // Redirect builds a redirect response. -func Redirect(url string) *sr.Response { - return &sr.Response{ +func Redirect(url string) *types.Response { + return &types.Response{ Status: StatusTemporaryRedirect, Meta: url, } } // PermanentRedirect builds a response with a permanent redirect. -func PermanentRedirect(url string) *sr.Response { - return &sr.Response{ +func PermanentRedirect(url string) *types.Response { + return &types.Response{ Status: StatusPermanentRedirect, Meta: url, } } // Failure builds a temporary failure response from an error. -func Failure(err error) *sr.Response { - return &sr.Response{ +func Failure(err error) *types.Response { + return &types.Response{ Status: StatusTemporaryFailure, Meta: err.Error(), } } // Unavailable build a "server unavailable" response. -func Unavailable(msg string) *sr.Response { - return &sr.Response{ +func Unavailable(msg string) *types.Response { + return &types.Response{ Status: StatusServerUnavailable, Meta: msg, } } // CGIError builds a "cgi error" response. -func CGIError(err string) *sr.Response { - return &sr.Response{ +func CGIError(err string) *types.Response { + return &types.Response{ Status: StatusCGIError, Meta: err, } } // ProxyError builds a proxy error response. -func ProxyError(msg string) *sr.Response { - return &sr.Response{ +func ProxyError(msg string) *types.Response { + return &types.Response{ Status: StatusProxyError, Meta: msg, } } // SlowDown builds a "slow down" response with the number of seconds until the resource is available. -func SlowDown(seconds int) *sr.Response { - return &sr.Response{ +func SlowDown(seconds int) *types.Response { + return &types.Response{ Status: StatusSlowDown, Meta: strconv.Itoa(seconds), } } // PermanentFailure builds a "permanent failure" from an error. -func PermanentFailure(err error) *sr.Response { - return &sr.Response{ +func PermanentFailure(err error) *types.Response { + return &types.Response{ Status: StatusPermanentFailure, Meta: err.Error(), } } // NotFound builds a "resource not found" response. -func NotFound(msg string) *sr.Response { - return &sr.Response{ +func NotFound(msg string) *types.Response { + return &types.Response{ Status: StatusNotFound, Meta: msg, } } // Gone builds a "resource gone" response. -func Gone(msg string) *sr.Response { - return &sr.Response{ +func Gone(msg string) *types.Response { + return &types.Response{ Status: StatusGone, Meta: msg, } } // RefuseProxy builds a "proxy request refused" response. -func RefuseProxy(msg string) *sr.Response { - return &sr.Response{ +func RefuseProxy(msg string) *types.Response { + return &types.Response{ Status: StatusProxyRequestRefused, Meta: msg, } } // BadRequest builds a "bad request" response. -func BadRequest(msg string) *sr.Response { - return &sr.Response{ +func BadRequest(msg string) *types.Response { + return &types.Response{ Status: StatusBadRequest, Meta: msg, } } // RequireCert builds a "client certificate required" response. -func RequireCert(msg string) *sr.Response { - return &sr.Response{ +func RequireCert(msg string) *types.Response { + return &types.Response{ Status: StatusClientCertificateRequired, Meta: msg, } } // CertAuthFailure builds a "certificate not authorized" response. -func CertAuthFailure(msg string) *sr.Response { - return &sr.Response{ +func CertAuthFailure(msg string) *types.Response { + return &types.Response{ Status: StatusCertificateNotAuthorized, Meta: msg, } } // CertInvalid builds a "client certificate not valid" response. -func CertInvalid(msg string) *sr.Response { - return &sr.Response{ +func CertInvalid(msg string) *types.Response { + return &types.Response{ Status: StatusCertificateNotValid, Meta: msg, } @@ -258,7 +258,7 @@ var InvalidResponseHeaderLine = errors.New("Invalid response header line.") // ParseResponse parses a complete gemini response from a reader. // // The reader must contain only one gemini response. -func ParseResponse(rdr io.Reader) (*sr.Response, error) { +func ParseResponse(rdr io.Reader) (*types.Response, error) { bufrdr := bufio.NewReader(rdr) hdrLine, err := bufrdr.ReadBytes('\n') @@ -278,14 +278,14 @@ func ParseResponse(rdr io.Reader) (*sr.Response, error) { return nil, InvalidResponseHeaderLine } - return &sr.Response{ - Status: sr.Status(status), + return &types.Response{ + Status: types.Status(status), Meta: string(hdrLine[3:]), Body: bufrdr, }, nil } -func NewResponseReader(response *sr.Response) sr.ResponseReader { +func NewResponseReader(response *types.Response) types.ResponseReader { return &responseReader{ Response: response, once: &sync.Once{}, @@ -293,7 +293,7 @@ func NewResponseReader(response *sr.Response) sr.ResponseReader { } type responseReader struct { - *sr.Response + *types.Response reader io.Reader once *sync.Once } diff --git a/gemini/response_test.go b/gemini/response_test.go index 00166dd..784e00a 100644 --- a/gemini/response_test.go +++ b/gemini/response_test.go @@ -6,15 +6,15 @@ import ( "io" "testing" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" "tildegit.org/tjp/sliderule/gemini" ) func TestBuildResponses(t *testing.T) { table := []struct { name string - response *sr.Response - status sr.Status + response *types.Response + status types.Status meta string body string }{ @@ -154,7 +154,7 @@ func TestBuildResponses(t *testing.T) { func TestParseResponses(t *testing.T) { table := []struct { input string - status sr.Status + status types.Status meta string body string err error @@ -233,7 +233,7 @@ func TestParseResponses(t *testing.T) { func TestResponseClose(t *testing.T) { body := &rdCloser{Buffer: bytes.NewBufferString("the body here")} - resp := &sr.Response{ + resp := &types.Response{ Status: gemini.StatusSuccess, Meta: "text/gemini", Body: body, @@ -247,7 +247,7 @@ func TestResponseClose(t *testing.T) { t.Error("response body was not closed by response.Close()") } - resp = &sr.Response{ + resp = &types.Response{ Status: gemini.StatusInput, Meta: "give me more", } @@ -270,8 +270,8 @@ func (rc *rdCloser) Close() error { func TestResponseWriteTo(t *testing.T) { // invariant under test: WriteTo() sends the same bytes as Read() - clone := func(resp *sr.Response) *sr.Response { - other := &sr.Response{ + clone := func(resp *types.Response) *types.Response { + other := &types.Response{ Status: resp.Status, Meta: resp.Meta, } @@ -297,7 +297,7 @@ func TestResponseWriteTo(t *testing.T) { table := []struct { name string - response *sr.Response + response *types.Response }{ { name: "simple success", diff --git a/gemini/roundtrip_test.go b/gemini/roundtrip_test.go index 0aa5648..50c1962 100644 --- a/gemini/roundtrip_test.go +++ b/gemini/roundtrip_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" "tildegit.org/tjp/sliderule/gemini" ) @@ -20,7 +20,7 @@ func TestRoundTrip(t *testing.T) { tlsConf, err := gemini.FileTLS("./testdata/server.crt", "./testdata/server.key") require.Nil(t, err) - handler := sr.HandlerFunc(func(ctx context.Context, req *sr.Request) *sr.Response { + handler := types.HandlerFunc(func(ctx context.Context, req *types.Request) *types.Response { return gemini.Success("text/gemini", bytes.NewBufferString("you've found my page")) }) @@ -36,7 +36,7 @@ func TestRoundTrip(t *testing.T) { require.Nil(t, err) cli := gemini.NewClient(testClientTLS()) - response, err := cli.RoundTrip(&sr.Request{URL: u}) + response, err := cli.RoundTrip(&types.Request{URL: u}) require.Nil(t, err) assert.Equal(t, gemini.StatusSuccess, response.Status) @@ -54,7 +54,7 @@ func TestTitanRequest(t *testing.T) { require.Nil(t, err) invoked := false - handler := sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { + handler := types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response { invoked = true body := gemini.GetTitanRequestBody(request) diff --git a/gemini/serve.go b/gemini/serve.go index 9173212..6fee458 100644 --- a/gemini/serve.go +++ b/gemini/serve.go @@ -11,7 +11,7 @@ import ( "strconv" "strings" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" "tildegit.org/tjp/sliderule/internal" "tildegit.org/tjp/sliderule/logging" ) @@ -19,7 +19,7 @@ import ( type server struct { internal.Server - handler sr.Handler + handler types.Handler } func (s server) Protocol() string { return "GEMINI" } @@ -30,10 +30,10 @@ func NewServer( hostname string, network string, address string, - handler sr.Handler, + handler types.Handler, errorLog logging.Logger, tlsConfig *tls.Config, -) (sr.Server, error) { +) (types.Server, error) { s := &server{handler: handler} hostname = internal.JoinDefaultPort(hostname, "1965") @@ -53,7 +53,7 @@ func NewServer( func (s *server) handleConn(conn net.Conn) { buf := bufio.NewReader(conn) - var response *sr.Response + var response *types.Response request, err := ParseRequest(buf) if err != nil { response = BadRequest(err.Error()) @@ -112,9 +112,9 @@ func sizeParam(path string) (int, error) { // Optionally, it will also allow through titan:// requests. // // Filtered requests will be turned away with a 53 response "proxy request refused". -func GeminiOnly(allowTitan bool) sr.Middleware { - return func(inner sr.Handler) sr.Handler { - return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { +func GeminiOnly(allowTitan bool) types.Middleware { + return func(inner types.Handler) types.Handler { + return types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response { if request.Scheme == "gemini" || (allowTitan && request.Scheme == "titan") { return inner.Handle(ctx, request) } diff --git a/gopher/client.go b/gopher/client.go index 163d0cd..5ef54ff 100644 --- a/gopher/client.go +++ b/gopher/client.go @@ -7,7 +7,7 @@ import ( "net" neturl "net/url" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // Client is used for sending gopher requests and producing the responses. @@ -18,7 +18,7 @@ import ( type Client struct{} // RoundTrip sends a single gopher request and returns its response. -func (c Client) RoundTrip(request *sr.Request) (*sr.Response, error) { +func (c Client) RoundTrip(request *types.Request) (*types.Response, error) { if request.Scheme != "gopher" && request.Scheme != "" { return nil, errors.New("non-gopher protocols not supported") } @@ -52,14 +52,14 @@ func (c Client) RoundTrip(request *sr.Request) (*sr.Response, error) { return nil, err } - return &sr.Response{Body: bytes.NewBuffer(response)}, nil + return &types.Response{Body: bytes.NewBuffer(response)}, nil } // Fetch parses a URL string and fetches the gopher resource. -func (c Client) Fetch(url string) (*sr.Response, error) { +func (c Client) Fetch(url string) (*types.Response, error) { u, err := neturl.Parse(url) if err != nil { return nil, err } - return c.RoundTrip(&sr.Request{URL: u}) + return c.RoundTrip(&types.Request{URL: u}) } diff --git a/gopher/request.go b/gopher/request.go index 4aac218..eef8262 100644 --- a/gopher/request.go +++ b/gopher/request.go @@ -8,11 +8,11 @@ import ( "path" "strings" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // ParseRequest parses a gopher protocol request into a sliderule.Request object. -func ParseRequest(rdr io.Reader) (*sr.Request, error) { +func ParseRequest(rdr io.Reader) (*types.Request, error) { selector, search, err := readFullRequest(rdr) if err != nil { return nil, err @@ -22,7 +22,7 @@ func ParseRequest(rdr io.Reader) (*sr.Request, error) { selector = "/" + selector } - return &sr.Request{ + return &types.Request{ URL: &url.URL{ Scheme: "gopher", Path: path.Clean(selector), diff --git a/gopher/response.go b/gopher/response.go index 566623f..527e742 100644 --- a/gopher/response.go +++ b/gopher/response.go @@ -6,49 +6,49 @@ import ( "io" "sync" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // The Canonical gopher item types. const ( - TextFileType sr.Status = '0' - MenuType sr.Status = '1' - CSOPhoneBookType sr.Status = '2' - ErrorType sr.Status = '3' - MacBinHexType sr.Status = '4' - DosBinType sr.Status = '5' - UuencodedType sr.Status = '6' - SearchType sr.Status = '7' - TelnetSessionType sr.Status = '8' - BinaryFileType sr.Status = '9' - MirrorServerType sr.Status = '+' - GifFileType sr.Status = 'g' - ImageFileType sr.Status = 'I' - Telnet3270Type sr.Status = 'T' + TextFileType types.Status = '0' + MenuType types.Status = '1' + CSOPhoneBookType types.Status = '2' + ErrorType types.Status = '3' + MacBinHexType types.Status = '4' + DosBinType types.Status = '5' + UuencodedType types.Status = '6' + SearchType types.Status = '7' + TelnetSessionType types.Status = '8' + BinaryFileType types.Status = '9' + MirrorServerType types.Status = '+' + GifFileType types.Status = 'g' + ImageFileType types.Status = 'I' + Telnet3270Type types.Status = 'T' ) // The gopher+ types. const ( - BitmapType sr.Status = ':' - MovieFileType sr.Status = ';' - SoundFileType sr.Status = '<' + BitmapType types.Status = ':' + MovieFileType types.Status = ';' + SoundFileType types.Status = '<' ) // The various non-canonical gopher types. const ( - DocumentType sr.Status = 'd' - HTMLType sr.Status = 'h' - InfoMessageType sr.Status = 'i' - PngImageFileType sr.Status = 'p' - RtfDocumentType sr.Status = 'r' - WavSoundFileType sr.Status = 's' - PdfDocumentType sr.Status = 'P' - XmlDocumentType sr.Status = 'X' + DocumentType types.Status = 'd' + HTMLType types.Status = 'h' + InfoMessageType types.Status = 'i' + PngImageFileType types.Status = 'p' + RtfDocumentType types.Status = 'r' + WavSoundFileType types.Status = 's' + PdfDocumentType types.Status = 'P' + XmlDocumentType types.Status = 'X' ) // MapItem is a single item in a gophermap. type MapItem struct { - Type sr.Status + Type types.Status Display string Selector string Hostname string @@ -70,8 +70,8 @@ func (mi MapItem) String() string { // Response builds a response which contains just this single MapItem. // // Meta in the response will be a pointer to the MapItem. -func (mi *MapItem) Response() *sr.Response { - return &sr.Response{ +func (mi *MapItem) Response() *types.Response { + return &types.Response{ Status: mi.Type, Meta: &mi, Body: bytes.NewBufferString(mi.String() + ".\r\n"), @@ -89,8 +89,8 @@ func (md MapDocument) String() string { // Response builds a gopher response containing the gophermap. // // Meta will be the MapDocument itself. -func (md MapDocument) Response() *sr.Response { - return &sr.Response{ +func (md MapDocument) Response() *types.Response { + return &types.Response{ Status: DocumentType, Meta: md, Body: md.serialize(), @@ -119,12 +119,12 @@ func Error(err error) *MapItem { // File builds a minimal response delivering a file's contents. // // Meta is nil and Status is 0 in this response. -func File(status sr.Status, contents io.Reader) *sr.Response { - return &sr.Response{Status: status, Body: contents} +func File(status types.Status, contents io.Reader) *types.Response { + return &types.Response{Status: status, Body: contents} } // NewResponseReader produces a reader which supports reading gopher protocol responses. -func NewResponseReader(response *sr.Response) sr.ResponseReader { +func NewResponseReader(response *types.Response) types.ResponseReader { return &responseReader{ Response: response, once: &sync.Once{}, @@ -132,7 +132,7 @@ func NewResponseReader(response *sr.Response) sr.ResponseReader { } type responseReader struct { - *sr.Response + *types.Response reader io.Reader once *sync.Once } diff --git a/gopher/serve.go b/gopher/serve.go index 7b32f2f..56b38f6 100644 --- a/gopher/serve.go +++ b/gopher/serve.go @@ -7,14 +7,14 @@ import ( "io" "net" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" "tildegit.org/tjp/sliderule/internal" "tildegit.org/tjp/sliderule/logging" ) type gopherServer struct { internal.Server - handler sr.Handler + handler types.Handler } func (gs gopherServer) Protocol() string { return "GOPHER" } @@ -25,9 +25,9 @@ func NewServer( hostname string, network string, address string, - handler sr.Handler, + handler types.Handler, errLog logging.Logger, -) (sr.Server, error) { +) (types.Server, error) { gs := &gopherServer{handler: handler} hostname = internal.JoinDefaultPort(hostname, "70") @@ -43,7 +43,7 @@ func NewServer( } func (gs *gopherServer) handleConn(conn net.Conn) { - var response *sr.Response + var response *types.Response request, err := ParseRequest(conn) if err != nil { response = Error(errors.New("Malformed request.")).Response() diff --git a/handler.go b/handler.go index 1c2d6dc..f3c9edd 100644 --- a/handler.go +++ b/handler.go @@ -1,33 +1,19 @@ package sliderule -import "context" +import ( + "context" -// Handler is a type which can turn a request into a response. -// -// Handle may return a nil response, in which case the Server is expected -// to build the protocol-appropriate "Not Found" response. -type Handler interface { - Handle(context.Context, *Request) *Response -} + "tildegit.org/tjp/sliderule/internal/types" +) -type handlerFunc func(context.Context, *Request) *Response +type Handler = types.Handler +type Middleware = types.Middleware // HandlerFunc is a wrapper to allow using a function as a Handler. func HandlerFunc(f func(context.Context, *Request) *Response) Handler { - return handlerFunc(f) -} - -// Handle implements Handler. -func (f handlerFunc) Handle(ctx context.Context, request *Request) *Response { - return f(ctx, request) + return types.HandlerFunc(f) } -// Middleware is a handler decorator. -// -// It returns a handler which may call the passed-in handler or not, or may -// transform the request or response in some way. -type Middleware func(Handler) Handler - // FallthroughHandler builds a handler which tries multiple child handlers. // // The returned handler will invoke each of the passed-in handlers in order, diff --git a/internal/types/handler.go b/internal/types/handler.go new file mode 100644 index 0000000..4805c57 --- /dev/null +++ b/internal/types/handler.go @@ -0,0 +1,28 @@ +package types + +import "context" + +// Handler is a type which can turn a request into a response. +// +// Handle may return a nil response, in which case the Server is expected +// to build the protocol-appropriate "Not Found" response. +type Handler interface { + Handle(context.Context, *Request) *Response +} + +// Middleware is a handler decorator. +// +// It returns a handler which may call the passed-in handler or not, or may +// transform the request or response in some way. +type Middleware func(Handler) Handler + +func HandlerFunc(f func(context.Context, *Request) *Response) Handler { + return handlerFunc(f) +} + +// Handle implements Handler. +func (f handlerFunc) Handle(ctx context.Context, request *Request) *Response { + return f(ctx, request) +} + +type handlerFunc func(context.Context, *Request) *Response diff --git a/internal/types/request.go b/internal/types/request.go new file mode 100644 index 0000000..e3d2e6f --- /dev/null +++ b/internal/types/request.go @@ -0,0 +1,49 @@ +package types + +import ( + "crypto/tls" + "net" + "net/url" +) + +// Request represents a request over any small web protocol. +// +// Because protocols have so many differences, this type represents a +// greatest common denominator of request/response-oriented protocols. +type Request struct { + // URL is the specific URL being fetched by the request. + *url.URL + + // Server is the server which received the request. + // + // This is only populated in servers. + // It is unused on the client end. + Server Server + + // Meta is a place for opaque data. + // + // Look for helper methods in protocol packages to use it appropriately + // for the protocol. + Meta any + + // RemoteAddr is the address of the other side of the connection. + // + // This will be the server address for clients, or the connecting + // client's address in servers. + // + // Be aware though that proxies (and reverse proxies) can confuse this. + RemoteAddr net.Addr + + // TLSState contains information about the TLS encryption over the connection. + // + // This includes peer certificates and version information. + TLSState *tls.ConnectionState +} + +// UnescapedQuery performs %XX unescaping on the URL query segment. +// +// Like URL.Query(), it silently drops malformed %-encoded sequences. +func (req Request) UnescapedQuery() string { + unescaped, _ := url.QueryUnescape(req.RawQuery) + return unescaped +} diff --git a/internal/types/response.go b/internal/types/response.go new file mode 100644 index 0000000..26dda05 --- /dev/null +++ b/internal/types/response.go @@ -0,0 +1,35 @@ +package types + +import "io" + +// Status is the integer status code of a response. +type Status int + +// Response contains the data in a response over the small web. +// +// Because protocols have so many differences, this type represents a +// greatest common denominator of request/response-oriented protocols. +type Response struct { + // Status is the status code of the response. + Status Status + + // Meta contains status-specific additional information. + Meta any + + // Body is the response body, if any. + Body io.Reader +} + +func (response *Response) Close() error { + if cl, ok := response.Body.(io.Closer); ok { + return cl.Close() + } + return nil +} + +// ResponseReader is an object which can serialize a response to a protocol. +type ResponseReader interface { + io.Reader + io.WriterTo + io.Closer +} diff --git a/internal/types/server.go b/internal/types/server.go new file mode 100644 index 0000000..80d12b5 --- /dev/null +++ b/internal/types/server.go @@ -0,0 +1,42 @@ +package types + +// Server is a type which can serve a protocol. +type Server interface { + // Serve blocks listening for connections on an interface. + // + // It will only return after Close() has been called. + Serve() error + + // Close initiates a graceful shutdown of the server. + // + // It blocks until all resources have been cleaned up and all + // outstanding requests have been handled and responses sent. + Close() + + // Closed indicates whether Close has been called. + // + // It may be true even if the graceful shutdown procedure + // hasn't yet completed. + Closed() bool + + // Protocol returns the protocol being served by the server. + Protocol() string + + // Network returns the network type on which the server is running. + Network() string + + // Address returns the address on which the server is listening. + Address() string + + // Hostname returns just the hostname portion of the listen address. + Hostname() string + + // Port returns the port on which the server is listening. + // + // It will return the empty string if the network type does not + // have ports (unix sockets, for example). + Port() string + + // LogError sends a log message to the server's error log. + LogError(keyvals ...any) error +} diff --git a/logging/middleware.go b/logging/middleware.go index 693cb2f..2d0e5b9 100644 --- a/logging/middleware.go +++ b/logging/middleware.go @@ -8,12 +8,12 @@ import ( "io" "time" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) -func LogRequests(logger Logger) sr.Middleware { - return func(inner sr.Handler) sr.Handler { - return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { +func LogRequests(logger Logger) types.Middleware { + return func(inner types.Handler) types.Handler { + return types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response { start := time.Now() response := inner.Handle(ctx, request) if response != nil { @@ -38,7 +38,7 @@ func LogRequests(logger Logger) sr.Middleware { } } -func clientFingerprint(request *sr.Request) (string, bool) { +func clientFingerprint(request *types.Request) (string, bool) { if request.TLSState == nil || len(request.TLSState.PeerCertificates) == 0 { return "", false } @@ -48,8 +48,8 @@ func clientFingerprint(request *sr.Request) (string, bool) { } type loggedResponseBody struct { - request *sr.Request - response *sr.Response + request *types.Request + response *types.Response body io.Reader start time.Time @@ -111,7 +111,7 @@ func (lwtr loggedWriteToResponseBody) WriteTo(dst io.Writer) (int64, error) { return n, err } -func loggingBody(logger Logger, request *sr.Request, response *sr.Response, start time.Time) io.Reader { +func loggingBody(logger Logger, request *types.Request, response *types.Response, start time.Time) io.Reader { body := &loggedResponseBody{ request: request, response: response, diff --git a/request.go b/request.go index dce8acb..c934b30 100644 --- a/request.go +++ b/request.go @@ -1,49 +1,5 @@ package sliderule -import ( - "crypto/tls" - "net" - "net/url" -) +import "tildegit.org/tjp/sliderule/internal/types" -// Request represents a request over any small web protocol. -// -// Because protocols have so many differences, this type represents a -// greatest common denominator of request/response-oriented protocols. -type Request struct { - // URL is the specific URL being fetched by the request. - *url.URL - - // Server is the server which received the request. - // - // This is only populated in servers. - // It is unused on the client end. - Server Server - - // Meta is a place for opaque data. - // - // Look for helper methods in protocol packages to use it appropriately - // for the protocol. - Meta any - - // RemoteAddr is the address of the other side of the connection. - // - // This will be the server address for clients, or the connecting - // client's address in servers. - // - // Be aware though that proxies (and reverse proxies) can confuse this. - RemoteAddr net.Addr - - // TLSState contains information about the TLS encryption over the connection. - // - // This includes peer certificates and version information. - TLSState *tls.ConnectionState -} - -// UnescapedQuery performs %XX unescaping on the URL query segment. -// -// Like URL.Query(), it silently drops malformed %-encoded sequences. -func (req Request) UnescapedQuery() string { - unescaped, _ := url.QueryUnescape(req.RawQuery) - return unescaped -} +type Request = types.Request diff --git a/response.go b/response.go index 249e08f..bb7c2bb 100644 --- a/response.go +++ b/response.go @@ -1,35 +1,7 @@ package sliderule -import "io" +import "tildegit.org/tjp/sliderule/internal/types" -// Status is the integer status code of a response. -type Status int - -// Response contains the data in a response over the small web. -// -// Because protocols have so many differences, this type represents a -// greatest common denominator of request/response-oriented protocols. -type Response struct { - // Status is the status code of the response. - Status Status - - // Meta contains status-specific additional information. - Meta any - - // Body is the response body, if any. - Body io.Reader -} - -func (response *Response) Close() error { - if cl, ok := response.Body.(io.Closer); ok { - return cl.Close() - } - return nil -} - -// ResponseReader is an object which can serialize a response to a protocol. -type ResponseReader interface { - io.Reader - io.WriterTo - io.Closer -} +type Status = types.Status +type Response = types.Response +type ResponseReader = types.ResponseReader diff --git a/server.go b/server.go index 1562f56..5bb8c97 100644 --- a/server.go +++ b/server.go @@ -1,42 +1,5 @@ package sliderule -// Server is a type which can serve a protocol. -type Server interface { - // Serve blocks listening for connections on an interface. - // - // It will only return after Close() has been called. - Serve() error +import "tildegit.org/tjp/sliderule/internal/types" - // Close initiates a graceful shutdown of the server. - // - // It blocks until all resources have been cleaned up and all - // outstanding requests have been handled and responses sent. - Close() - - // Closed indicates whether Close has been called. - // - // It may be true even if the graceful shutdown procedure - // hasn't yet completed. - Closed() bool - - // Protocol returns the protocol being served by the server. - Protocol() string - - // Network returns the network type on which the server is running. - Network() string - - // Address returns the address on which the server is listening. - Address() string - - // Hostname returns just the hostname portion of the listen address. - Hostname() string - - // Port returns the port on which the server is listening. - // - // It will return the empty string if the network type does not - // have ports (unix sockets, for example). - Port() string - - // LogError sends a log message to the server's error log. - LogError(keyvals ...any) error -} +type Server = types.Server diff --git a/spartan/client.go b/spartan/client.go index 9547f74..e3025ee 100644 --- a/spartan/client.go +++ b/spartan/client.go @@ -8,7 +8,7 @@ import ( neturl "net/url" "strconv" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // Client is used for sending spartan requests and receiving responses. @@ -32,7 +32,7 @@ const DefaultMaxRedirects int = 2 var ExceededMaxRedirects = errors.New("spartan.Client: exceeded MaxRedirects") // RoundTrip sends a single spartan request and returns its response. -func (c Client) RoundTrip(request *sr.Request) (*sr.Response, error) { +func (c Client) RoundTrip(request *types.Request) (*types.Response, error) { if request.Scheme != "spartan" && request.Scheme != "" { return nil, errors.New("non-spartan protocols not supported") } @@ -89,14 +89,14 @@ func (c Client) RoundTrip(request *sr.Request) (*sr.Response, error) { // Fetch parses a URL string and fetches the spartan resource. // // It will resolve any redirects along the way, up to client.MaxRedirects. -func (c Client) Fetch(url string) (*sr.Response, error) { +func (c Client) Fetch(url string) (*types.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(&sr.Request{URL: u}) + response, err := c.RoundTrip(&types.Request{URL: u}) if err != nil { return nil, err } diff --git a/spartan/request.go b/spartan/request.go index c056af0..c88a20b 100644 --- a/spartan/request.go +++ b/spartan/request.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) var ( @@ -23,7 +23,7 @@ var ( // ParseRequest parses a single spartan request and the indicated content-length from a reader. // // If ther reader artument is a *bufio.Reader, it will only read a single line from it. -func ParseRequest(rdr io.Reader) (*sr.Request, int, error) { +func ParseRequest(rdr io.Reader) (*types.Request, int, error) { bufrdr, ok := rdr.(*bufio.Reader) if !ok { bufrdr = bufio.NewReader(rdr) @@ -52,7 +52,7 @@ func ParseRequest(rdr io.Reader) (*sr.Request, int, error) { return nil, 0, err } - return &sr.Request{ + return &types.Request{ URL: &url.URL{ Scheme: "spartan", Host: host, @@ -65,7 +65,7 @@ func ParseRequest(rdr io.Reader) (*sr.Request, int, error) { // GetRequestContentLength reads the remaining un-read number of bytes in a request body. // // It will immediately return 0 if there is no request body. -func GetRequestContentLength(request *sr.Request) int { +func GetRequestContentLength(request *types.Request) int { if lr, ok := request.Meta.(*io.LimitedReader); ok { return int(lr.N) } @@ -75,7 +75,7 @@ func GetRequestContentLength(request *sr.Request) int { // GetRequestBody returns a reader of the spartan request body. // // It will return nil if the request has no body. -func GetRequestBody(request *sr.Request) io.Reader { +func GetRequestBody(request *types.Request) io.Reader { if rdr, ok := request.Meta.(io.Reader); ok { return rdr } @@ -88,7 +88,7 @@ func GetRequestBody(request *sr.Request) io.Reader { // // This function will read the entire contents into memory unless // the reader is already an *io.LimitedReader. -func SetRequestBody(request *sr.Request, body io.Reader) error { +func SetRequestBody(request *types.Request, body io.Reader) error { if rdr, ok := body.(*io.LimitedReader); ok { request.Meta = rdr return nil diff --git a/spartan/response.go b/spartan/response.go index aeddd68..df565f9 100644 --- a/spartan/response.go +++ b/spartan/response.go @@ -8,20 +8,20 @@ import ( "strconv" "sync" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" ) // The spartan response types. const ( - StatusSuccess sr.Status = 2 - StatusRedirect sr.Status = 3 - StatusClientError sr.Status = 4 - StatusServerError sr.Status = 5 + StatusSuccess types.Status = 2 + StatusRedirect types.Status = 3 + StatusClientError types.Status = 4 + StatusServerError types.Status = 5 ) // Success builds a successful spartan response. -func Success(mediatype string, body io.Reader) *sr.Response { - return &sr.Response{ +func Success(mediatype string, body io.Reader) *types.Response { + return &types.Response{ Status: StatusSuccess, Meta: mediatype, Body: body, @@ -29,24 +29,24 @@ func Success(mediatype string, body io.Reader) *sr.Response { } // Redirect builds a spartan redirect response. -func Redirect(url string) *sr.Response { - return &sr.Response{ +func Redirect(url string) *types.Response { + return &types.Response{ Status: StatusRedirect, Meta: url, } } // ClientError builds a "client error" spartan response. -func ClientError(err error) *sr.Response { - return &sr.Response{ +func ClientError(err error) *types.Response { + return &types.Response{ Status: StatusClientError, Meta: err.Error(), } } // ServerError builds a "server error" spartan response. -func ServerError(err error) *sr.Response { - return &sr.Response{ +func ServerError(err error) *types.Response { + return &types.Response{ Status: StatusServerError, Meta: err.Error(), } @@ -58,7 +58,7 @@ var InvalidResponseHeaderLine = errors.New("Invalid response header line.") // InvalidResponseLineEnding indicates that a spartan response header didn't end with "\r\n". var InvalidResponseLineEnding = errors.New("Invalid response line ending.") -func ParseResponse(rdr io.Reader) (*sr.Response, error) { +func ParseResponse(rdr io.Reader) (*types.Response, error) { bufrdr := bufio.NewReader(rdr) hdrLine, err := bufrdr.ReadString('\n') @@ -74,15 +74,15 @@ func ParseResponse(rdr io.Reader) (*sr.Response, error) { return nil, InvalidResponseHeaderLine } - return &sr.Response{ - Status: sr.Status(status), + return &types.Response{ + Status: types.Status(status), Meta: hdrLine[2 : len(hdrLine)-2], Body: bufrdr, }, nil } // NewResponseReader builds a reader for a response. -func NewResponseReader(response *sr.Response) sr.ResponseReader { +func NewResponseReader(response *types.Response) types.ResponseReader { return &responseReader{ Response: response, once: &sync.Once{}, @@ -90,7 +90,7 @@ func NewResponseReader(response *sr.Response) sr.ResponseReader { } type responseReader struct { - *sr.Response + *types.Response reader io.Reader once *sync.Once } diff --git a/spartan/serve.go b/spartan/serve.go index a477a45..4b37f6a 100644 --- a/spartan/serve.go +++ b/spartan/serve.go @@ -8,14 +8,14 @@ import ( "io" "net" - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/internal/types" "tildegit.org/tjp/sliderule/internal" "tildegit.org/tjp/sliderule/logging" ) type spartanServer struct { internal.Server - handler sr.Handler + handler types.Handler } func (ss spartanServer) Protocol() string { return "SPARTAN" } @@ -26,9 +26,9 @@ func NewServer( hostname string, network string, address string, - handler sr.Handler, + handler types.Handler, errLog logging.Logger, -) (sr.Server, error) { +) (types.Server, error) { ss := &spartanServer{handler: handler} hostname = internal.JoinDefaultPort(hostname, "300") @@ -46,7 +46,7 @@ func NewServer( func (ss *spartanServer) handleConn(conn net.Conn) { buf := bufio.NewReader(conn) - var response *sr.Response + var response *types.Response request, clen, err := ParseRequest(buf) if err != nil { response = ClientError(err) -- cgit v1.2.3