1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-12-23 22:11:10 +02:00
Files
imgproxy/storage/reader.go
Victor Sokolov d5370d8077 Adds range checks to storage tests (#1566)
* Adds range checks to storage test

* Storage tests unified
2025-11-04 17:05:12 +01:00

127 lines
3.4 KiB
Go

package storage
import (
"context"
"io"
"net/http"
"strconv"
"strings"
"github.com/imgproxy/imgproxy/v3/httpheaders"
)
// Reader represents a generic storage interface, which can read
// objects from a storage backend.
type Reader interface {
// GetObject retrieves an object from the storage and returns
// ObjectReader with the result.
GetObject(
ctx context.Context,
reqHeader http.Header,
bucket, key, query string,
) (*ObjectReader, error)
}
// ObjectReader represents a generic reader for a storage object.
// It can be in any state: success, error, not found, etc.
// It can be converted to HTTP response or used as-is.
type ObjectReader struct {
Status int // HTTP status code
Headers http.Header // Response headers harvested from the engine response
Body io.ReadCloser // Response body reader
contentLength int64
}
// NewObjectOK creates a new Reader with a 200 OK status.
func NewObjectOK(headers http.Header, body io.ReadCloser) *ObjectReader {
return &ObjectReader{
Status: http.StatusOK,
Headers: headers,
Body: body,
contentLength: -1, // is set in Response()
}
}
// NewObjectPartialContent creates a new Reader with a 206 Partial Content status.
func NewObjectPartialContent(headers http.Header, body io.ReadCloser) *ObjectReader {
return &ObjectReader{
Status: http.StatusPartialContent,
Headers: headers,
Body: body,
contentLength: -1, // is set in Response()
}
}
// NewObjectNotFound creates a new Reader with a 404 Not Found status.
func NewObjectNotFound(message string) *ObjectReader {
return NewObjectError(http.StatusNotFound, message)
}
// NewObjectError creates a new Reader with a custom status code
func NewObjectError(statusCode int, message string) *ObjectReader {
return &ObjectReader{
Status: statusCode,
Body: io.NopCloser(strings.NewReader(message)),
Headers: http.Header{httpheaders.ContentType: {"text/plain"}},
contentLength: int64(len(message)),
}
}
// NewObjectNotModified creates a new Reader with a 304 Not Modified status.
func NewObjectNotModified(headers http.Header) *ObjectReader {
// Copy headers relevant to NotModified response only
nmHeaders := make(http.Header)
httpheaders.Copy(
headers,
nmHeaders,
[]string{httpheaders.Etag, httpheaders.LastModified},
)
return &ObjectReader{
Status: http.StatusNotModified,
Headers: nmHeaders,
contentLength: 0,
}
}
// NewInvalidRang creates a new Reader with a 416 Range Not Satisfiable status.
func NewObjectInvalidRange() *ObjectReader {
return &ObjectReader{
Status: http.StatusRequestedRangeNotSatisfiable,
contentLength: 0,
}
}
// ContentLength returns the content length of the response.
func (r *ObjectReader) ContentLength() int64 {
if r.contentLength > 0 {
return r.contentLength
}
h := r.Headers.Get(httpheaders.ContentLength)
if len(h) > 0 {
p, err := strconv.ParseInt(h, 10, 64)
if err != nil {
return p
}
}
return -1
}
// Response converts Reader to http.Response
func (r *ObjectReader) Response(req *http.Request) *http.Response {
return &http.Response{
Status: http.StatusText(r.Status),
StatusCode: r.Status,
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
Header: r.Headers,
Body: r.Body,
Close: true,
Request: req,
ContentLength: r.ContentLength(),
}
}