summaryrefslogtreecommitdiff
path: root/gemini/response.go
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-01-17 15:59:29 -0700
committertjpcc <tjp@ctrl-c.club>2023-01-17 15:59:29 -0700
commit2ef530daa47b301a40c1ee93cd43b8f36fc68c0b (patch)
treeb9753719f5f0e5312bb5008d40f40247ce14e15a /gemini/response.go
parent30e21f8513d49661cb6e1583d301e34e898d48a9 (diff)
pull request, response, handlers out of the gemini package
Diffstat (limited to 'gemini/response.go')
-rw-r--r--gemini/response.go204
1 files changed, 95 insertions, 109 deletions
diff --git a/gemini/response.go b/gemini/response.go
index 5b5ced4..0452462 100644
--- a/gemini/response.go
+++ b/gemini/response.go
@@ -6,65 +6,68 @@ import (
"errors"
"io"
"strconv"
+
+ "tildegit.org/tjp/gus"
)
-// StatusCategory represents the various types of responses.
-type StatusCategory int
+// ResponseCategory represents the various types of gemini responses.
+type ResponseCategory int
const (
- // StatusCategoryInput is for responses which request additional input.
+ // ResponseCategoryInput is for responses which request additional input.
//
// The META line will be the prompt to display to the user.
- StatusCategoryInput StatusCategory = iota*10 + 10
- // StatusCategorySuccess is for successful responses.
+ ResponseCategoryInput ResponseCategory = iota*10 + 10
+ // ResponseCategorySuccess is for successful responses.
//
// The META line will be the resource's mime type.
// This is the only response status which indicates the presence of a response body,
// and it will contain the resource itself.
- StatusCategorySuccess
- // StatusCategoryRedirect is for responses which direct the client to an alternative URL.
+ ResponseCategorySuccess
+ // ResponseCategoryRedirect is for responses which direct the client to an alternative URL.
//
// The META line will contain the new URL the client should try.
- StatusCategoryRedirect
- // StatusCategoryTemporaryFailure is for responses which indicate a transient server-side failure.
+ ResponseCategoryRedirect
+ // ResponseCategoryTemporaryFailure is for responses which indicate a transient server-side failure.
//
// The META line may contain a line with more information about the error.
- StatusCategoryTemporaryFailure
- // StatusCategoryPermanentFailure is for permanent failure responses.
+ ResponseCategoryTemporaryFailure
+ // ResponseCategoryPermanentFailure is for permanent failure responses.
//
// The META line may contain a line with more information about the error.
- StatusCategoryPermanentFailure
- // StatusCategoryCertificateRequired indicates client certificate related issues.
+ ResponseCategoryPermanentFailure
+ // ResponseCategoryCertificateRequired indicates client certificate related issues.
//
// The META line may contain a line with more information about the error.
- StatusCategoryCertificateRequired
+ ResponseCategoryCertificateRequired
)
-// Status is the integer status code of a gemini response.
-type Status int
+func ResponseCategoryForStatus(status gus.Status) ResponseCategory {
+ return ResponseCategory(status / 10)
+}
const (
// StatusInput indicates a required query parameter at the requested URL.
- StatusInput Status = Status(StatusCategoryInput) + iota
+ StatusInput gus.Status = gus.Status(ResponseCategoryInput) + iota
// StatusSensitiveInput indicates a sensitive query parameter is required.
StatusSensitiveInput
)
const (
// StatusSuccess is a successful response.
- StatusSuccess = Status(StatusCategorySuccess) + iota
+ StatusSuccess = gus.Status(ResponseCategorySuccess) + iota
)
const (
// StatusTemporaryRedirect indicates a temporary redirect to another URL.
- StatusTemporaryRedirect = Status(StatusCategoryRedirect) + iota
+ StatusTemporaryRedirect = gus.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 = Status(StatusCategoryTemporaryFailure) + iota
+ StatusTemporaryFailure = gus.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.
@@ -80,7 +83,7 @@ const (
const (
// StatusPermanentFailure is a server failure which should be expected to continue indefinitely.
- StatusPermanentFailure = Status(StatusCategoryPermanentFailure) + iota
+ StatusPermanentFailure = gus.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.
@@ -88,58 +91,37 @@ 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 = Status(StatusCategoryPermanentFailure) + 9
+ StatusBadRequest = gus.Status(ResponseCategoryPermanentFailure) + 9
)
const (
// StatusClientCertificateRequired is returned when a certificate was required but not provided.
- StatusClientCertificateRequired = Status(StatusCategoryCertificateRequired) + iota
+ StatusClientCertificateRequired = gus.Status(ResponseCategoryCertificateRequired) + iota
// StatusCertificateNotAuthorized means the certificate doesn't grant access to the requested resource.
StatusCertificateNotAuthorized
// StatusCertificateNotValid means the provided client certificate is invalid.
StatusCertificateNotValid
)
-// StatusCategory returns the category a specific status belongs to.
-func (s Status) Category() StatusCategory {
- return StatusCategory(s / 10)
-}
-
-// Response contains everything in a gemini protocol response.
-type Response struct {
- // Status is the status code of the response.
- Status Status
-
- // Meta is the status-specific line of additional information.
- Meta string
-
- // Body is the response body, if any.
- //
- // It is not guaranteed to be readable more than once.
- Body io.Reader
-
- reader io.Reader
-}
-
// Input builds an input-prompting response.
-func Input(prompt string) *Response {
- return &Response{
+func Input(prompt string) *gus.Response {
+ return &gus.Response{
Status: StatusInput,
Meta: prompt,
}
}
// SensitiveInput builds a password-prompting response.
-func SensitiveInput(prompt string) *Response {
- return &Response{
+func SensitiveInput(prompt string) *gus.Response {
+ return &gus.Response{
Status: StatusSensitiveInput,
Meta: prompt,
}
}
// Success builds a success response with resource body.
-func Success(mediatype string, body io.Reader) *Response {
- return &Response{
+func Success(mediatype string, body io.Reader) *gus.Response {
+ return &gus.Response{
Status: StatusSuccess,
Meta: mediatype,
Body: body,
@@ -147,120 +129,120 @@ func Success(mediatype string, body io.Reader) *Response {
}
// Redirect builds a redirect response.
-func Redirect(url string) *Response {
- return &Response{
+func Redirect(url string) *gus.Response {
+ return &gus.Response{
Status: StatusTemporaryRedirect,
Meta: url,
}
}
// PermanentRedirect builds a response with a permanent redirect.
-func PermanentRedirect(url string) *Response {
- return &Response{
+func PermanentRedirect(url string) *gus.Response {
+ return &gus.Response{
Status: StatusPermanentRedirect,
Meta: url,
}
}
// Failure builds a temporary failure response from an error.
-func Failure(err error) *Response {
- return &Response{
+func Failure(err error) *gus.Response {
+ return &gus.Response{
Status: StatusTemporaryFailure,
Meta: err.Error(),
}
}
// Unavailable build a "server unavailable" response.
-func Unavailable(msg string) *Response {
- return &Response{
+func Unavailable(msg string) *gus.Response {
+ return &gus.Response{
Status: StatusServerUnavailable,
Meta: msg,
}
}
// CGIError builds a "cgi error" response.
-func CGIError(err string) *Response {
- return &Response{
+func CGIError(err string) *gus.Response {
+ return &gus.Response{
Status: StatusCGIError,
Meta: err,
}
}
// ProxyError builds a proxy error response.
-func ProxyError(msg string) *Response {
- return &Response{
+func ProxyError(msg string) *gus.Response {
+ return &gus.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) *Response {
- return &Response{
+func SlowDown(seconds int) *gus.Response {
+ return &gus.Response{
Status: StatusSlowDown,
Meta: strconv.Itoa(seconds),
}
}
// PermanentFailure builds a "permanent failure" from an error.
-func PermanentFailure(err error) *Response {
- return &Response{
+func PermanentFailure(err error) *gus.Response {
+ return &gus.Response{
Status: StatusPermanentFailure,
Meta: err.Error(),
}
}
// NotFound builds a "resource not found" response.
-func NotFound(msg string) *Response {
- return &Response{
+func NotFound(msg string) *gus.Response {
+ return &gus.Response{
Status: StatusNotFound,
Meta: msg,
}
}
// Gone builds a "resource gone" response.
-func Gone(msg string) *Response {
- return &Response{
+func Gone(msg string) *gus.Response {
+ return &gus.Response{
Status: StatusGone,
Meta: msg,
}
}
// RefuseProxy builds a "proxy request refused" response.
-func RefuseProxy(msg string) *Response {
- return &Response{
+func RefuseProxy(msg string) *gus.Response {
+ return &gus.Response{
Status: StatusProxyRequestRefused,
Meta: msg,
}
}
// BadRequest builds a "bad request" response.
-func BadRequest(msg string) *Response {
- return &Response{
+func BadRequest(msg string) *gus.Response {
+ return &gus.Response{
Status: StatusBadRequest,
Meta: msg,
}
}
// RequireCert builds a "client certificate required" response.
-func RequireCert(msg string) *Response {
- return &Response{
+func RequireCert(msg string) *gus.Response {
+ return &gus.Response{
Status: StatusClientCertificateRequired,
Meta: msg,
}
}
// CertAuthFailure builds a "certificate not authorized" response.
-func CertAuthFailure(msg string) *Response {
- return &Response{
+func CertAuthFailure(msg string) *gus.Response {
+ return &gus.Response{
Status: StatusCertificateNotAuthorized,
Meta: msg,
}
}
// CertInvalid builds a "client certificate not valid" response.
-func CertInvalid(msg string) *Response {
- return &Response{
+func CertInvalid(msg string) *gus.Response {
+ return &gus.Response{
Status: StatusCertificateNotValid,
Meta: msg,
}
@@ -275,7 +257,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) (*Response, error) {
+func ParseResponse(rdr io.Reader) (*gus.Response, error) {
bufrdr := bufio.NewReader(rdr)
hdrLine, err := bufrdr.ReadBytes('\n')
@@ -295,53 +277,57 @@ func ParseResponse(rdr io.Reader) (*Response, error) {
return nil, InvalidResponseHeaderLine
}
- return &Response{
- Status: Status(status),
+ return &gus.Response{
+ Status: gus.Status(status),
Meta: string(hdrLine[3:]),
Body: bufrdr,
}, nil
}
-// Read implements io.Reader for Response.
-func (r *Response) Read(b []byte) (int, error) {
- r.ensureReader()
- return r.reader.Read(b)
+type ResponseReader interface {
+ io.Reader
+ io.WriterTo
+ io.Closer
}
-// WriteTo implements io.WriterTo for Response.
-func (r *Response) WriteTo(dst io.Writer) (int64, error) {
- r.ensureReader()
- return r.reader.(io.WriterTo).WriteTo(dst)
+func NewResponseReader(response *gus.Response) ResponseReader {
+ return &responseReader{ Response: response }
}
-// Close implements io.Closer and ensures the body gets closed.
-func (r *Response) Close() error {
- if r != nil {
- if cl, ok := r.Body.(io.Closer); ok {
- return cl.Close()
- }
- }
- return nil
+type responseReader struct {
+ *gus.Response
+ reader io.Reader
+}
+
+func (rdr *responseReader) Read(b []byte) (int, error) {
+ rdr.ensureReader()
+ return rdr.reader.Read(b)
+}
+
+func (rdr *responseReader) WriteTo(dst io.Writer) (int64, error) {
+ rdr.ensureReader()
+ return rdr.reader.(io.WriterTo).WriteTo(dst)
}
-func (r *Response) ensureReader() {
- if r.reader != nil {
+func (rdr *responseReader) ensureReader() {
+ if rdr.reader != nil {
return
}
- hdr := bytes.NewBuffer(r.headerLine())
- if r.Body != nil {
- r.reader = io.MultiReader(hdr, r.Body)
+ hdr := bytes.NewBuffer(rdr.headerLine())
+ if rdr.Body != nil {
+ rdr.reader = io.MultiReader(hdr, rdr.Body)
} else {
- r.reader = hdr
+ rdr.reader = hdr
}
}
-func (r Response) headerLine() []byte {
- buf := make([]byte, len(r.Meta)+5)
- _ = strconv.AppendInt(buf[:0], int64(r.Status), 10)
+func (rdr responseReader) headerLine() []byte {
+ meta := rdr.Meta.(string)
+ buf := make([]byte, len(meta)+5)
+ _ = strconv.AppendInt(buf[:0], int64(rdr.Status), 10)
buf[2] = ' '
- copy(buf[3:], r.Meta)
+ copy(buf[3:], meta)
buf[len(buf)-2] = '\r'
buf[len(buf)-1] = '\n'
return buf