status-go/vendor/github.com/anacrolix/missinggo/v2/resource/http.go

197 lines
3.9 KiB
Go
Raw Normal View History

2022-03-10 09:44:48 +00:00
package resource
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"time"
)
// Provides access to resources through a http.Client.
type HTTPProvider struct {
Client *http.Client
}
var _ Provider = &HTTPProvider{}
func (me *HTTPProvider) NewInstance(urlStr string) (r Instance, err error) {
_r := new(httpInstance)
_r.URL, err = url.Parse(urlStr)
if err != nil {
return
}
_r.Client = me.Client
if _r.Client == nil {
_r.Client = http.DefaultClient
}
r = _r
return
}
type httpInstance struct {
Client *http.Client
URL *url.URL
}
var _ Instance = &httpInstance{}
func mustNewRequest(method, urlStr string, body io.Reader) *http.Request {
req, err := http.NewRequest(method, urlStr, body)
if err != nil {
panic(err)
}
return req
}
func responseError(r *http.Response) error {
if r.StatusCode == http.StatusNotFound {
return os.ErrNotExist
}
return errors.New(r.Status)
}
func (me *httpInstance) Get() (ret io.ReadCloser, err error) {
resp, err := me.Client.Get(me.URL.String())
if err != nil {
return
}
if resp.StatusCode == http.StatusOK {
ret = resp.Body
return
}
resp.Body.Close()
err = responseError(resp)
return
}
func (me *httpInstance) Put(r io.Reader) (err error) {
resp, err := me.Client.Do(mustNewRequest("PUT", me.URL.String(), r))
if err != nil {
return
}
resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return
}
err = responseError(resp)
return
}
func (me *httpInstance) ReadAt(b []byte, off int64) (n int, err error) {
req := mustNewRequest("GET", me.URL.String(), nil)
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", off, off+int64(len(b))-1))
resp, err := me.Client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusPartialContent:
case http.StatusRequestedRangeNotSatisfiable:
err = io.EOF
return
default:
err = responseError(resp)
return
}
// TODO: This will crash if ContentLength was not provided (-1). Do
// something about that.
b = b[:resp.ContentLength]
return io.ReadFull(resp.Body, b)
}
func (me *httpInstance) WriteAt(b []byte, off int64) (n int, err error) {
req := mustNewRequest("PATCH", me.URL.String(), bytes.NewReader(b))
req.ContentLength = int64(len(b))
req.Header.Set("Content-Range", fmt.Sprintf("bytes=%d-%d", off, off+int64(len(b))-1))
resp, err := me.Client.Do(req)
if err != nil {
return
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err = responseError(resp)
}
n = len(b)
return
}
func (me *httpInstance) Stat() (fi os.FileInfo, err error) {
resp, err := me.Client.Head(me.URL.String())
if err != nil {
return
}
resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
err = os.ErrNotExist
return
}
if resp.StatusCode != http.StatusOK {
err = errors.New(resp.Status)
return
}
var _fi httpFileInfo
if h := resp.Header.Get("Last-Modified"); h != "" {
_fi.lastModified, err = time.Parse(http.TimeFormat, h)
if err != nil {
err = fmt.Errorf("error parsing Last-Modified header: %s", err)
return
}
}
if h := resp.Header.Get("Content-Length"); h != "" {
_fi.contentLength, err = strconv.ParseInt(h, 10, 64)
if err != nil {
err = fmt.Errorf("error parsing Content-Length header: %s", err)
return
}
}
fi = _fi
return
}
func (me *httpInstance) Delete() (err error) {
resp, err := me.Client.Do(mustNewRequest("DELETE", me.URL.String(), nil))
if err != nil {
return
}
err = responseError(resp)
resp.Body.Close()
return
}
type httpFileInfo struct {
lastModified time.Time
contentLength int64
}
var _ os.FileInfo = httpFileInfo{}
func (fi httpFileInfo) IsDir() bool {
return false
}
func (fi httpFileInfo) Mode() os.FileMode {
return 0
}
func (fi httpFileInfo) Name() string {
return ""
}
func (fi httpFileInfo) Size() int64 {
return fi.contentLength
}
func (fi httpFileInfo) ModTime() time.Time {
return fi.lastModified
}
func (fi httpFileInfo) Sys() interface{} {
return nil
}