move common types to an internal package
continuous-integration/drone Build is failing Details

This helps avoid import cycles.
This commit is contained in:
tjpcc 2023-08-12 09:40:39 -06:00
parent 57a31a9b2c
commit 23bc5f4fb7
28 changed files with 356 additions and 325 deletions

View File

@ -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

View File

@ -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,

View File

@ -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}
}

View File

@ -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")

View File

@ -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(

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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",

View File

@ -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)

View File

@ -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)
}

View File

@ -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})
}

View File

@ -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),

View File

@ -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
}

View File

@ -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()

View File

@ -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)
return types.HandlerFunc(f)
}
// Handle implements Handler.
func (f handlerFunc) Handle(ctx context.Context, request *Request) *Response {
return f(ctx, request)
}
// 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,

28
internal/types/handler.go Normal file
View File

@ -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

49
internal/types/request.go Normal file
View File

@ -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
}

View File

@ -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
}

42
internal/types/server.go Normal file
View File

@ -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
}

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)