move common types to an internal package
continuous-integration/drone Build is failing
Details
continuous-integration/drone Build is failing
Details
This helps avoid import cycles.
This commit is contained in:
parent
57a31a9b2c
commit
23bc5f4fb7
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
28
handler.go
28
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)
|
||||
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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
48
request.go
48
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
|
||||
|
|
36
response.go
36
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
|
||||
|
|
41
server.go
41
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue