697 lines
23 KiB
Go
697 lines
23 KiB
Go
package jsFetch
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"maps"
|
|
"net"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
"unicode"
|
|
|
|
"crypto/tls"
|
|
"mime/multipart"
|
|
"net/url"
|
|
"syscall/js"
|
|
|
|
"git.ailur.dev/ailur/jsStreams"
|
|
)
|
|
|
|
// Transport is here only for compatibility with the Go standard library.
|
|
// All fields are ignored in fetch.
|
|
type Transport struct {
|
|
// Proxy specifies a function to return a proxy for a given Request.
|
|
// It is ignored in fetch.
|
|
Proxy func(*Request) (*url.URL, error)
|
|
|
|
// OnProxyError specifies a function to handle errors that occur while fetching a proxy.
|
|
// It is ignored in fetch.
|
|
OnProxyError func(*Request, *url.URL, error)
|
|
|
|
// DialContext specifies the dial function for creating unencrypted TCP connections.
|
|
// It is ignored in fetch.
|
|
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
|
|
|
// Dial specifies the dial function for creating unencrypted TCP connections.
|
|
// It is ignored in fetch.
|
|
Dial func(network, addr string) (net.Conn, error)
|
|
|
|
// DialTLSContext specifies the dial function for creating TLS connections.
|
|
// It is ignored in fetch.
|
|
DialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
|
|
|
// DialTLS specifies the dial function for creating TLS connections.
|
|
// It is ignored in fetch.
|
|
DialTLS func(network, addr string) (net.Conn, error)
|
|
|
|
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
|
|
// It is ignored in fetch.
|
|
TLSClientConfig *tls.Config
|
|
|
|
// TLSHandshakeTimeout specifies the maximum amount of time waiting to wait for a TLS handshake.
|
|
// It is ignored in fetch.
|
|
TLSHandshakeTimeout time.Duration
|
|
|
|
// DisableKeepAlives specifies whether to disable keep-alive connections.
|
|
// It is ignored in fetch.
|
|
DisableKeepAlives bool
|
|
|
|
// DisableCompression specifies whether to disable compression.
|
|
// It is ignored in fetch.
|
|
DisableCompression bool
|
|
|
|
// MaxIdleConns specifies the maximum number of idle (keep-alive) connections to keep.
|
|
// It is ignored in fetch.
|
|
MaxIdleConns int
|
|
|
|
// MaxIdleConnsPerHost specifies the maximum number of idle (keep-alive) connections to keep per host.
|
|
// It is ignored in fetch.
|
|
MaxIdleConnsPerHost int
|
|
|
|
// IdleConnTimeout specifies the maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
|
|
// It is ignored in fetch.
|
|
IdleConnTimeout time.Duration
|
|
|
|
// ResponseHeaderTimeout specifies the maximum amount of time to wait for a response header.
|
|
// It is ignored in fetch.
|
|
ResponseHeaderTimeout time.Duration
|
|
|
|
// ExpectContinueTimeout specifies the maximum amount of time to wait for a 100-continue response.
|
|
// It is ignored in fetch.
|
|
ExpectContinueTimeout time.Duration
|
|
|
|
// TLSNextProto specifies a function to upgrade the connection to a different protocol.
|
|
// It is ignored in fetch.
|
|
TLSNextProto map[string]func(authority string, c net.Conn) RoundTripper
|
|
|
|
// ProxyConnectHeader specifies the headers to send to proxies during CONNECT requests.
|
|
// It is ignored in fetch.
|
|
ProxyConnectHeader Header
|
|
|
|
// MaxResponseHeaderBytes specifies the maximum number of bytes to read from the server's response headers.
|
|
// It is ignored in fetch.
|
|
MaxResponseHeaderBytes int
|
|
|
|
// WriteBufferSize specifies the size of the write buffer.
|
|
// It is ignored in fetch.
|
|
WriteBufferSize int
|
|
|
|
// ReadBufferSize specifies the size of the read buffer.
|
|
// It is ignored in fetch.
|
|
ReadBufferSize int
|
|
|
|
// ForceAttemptHTTP2 specifies whether to force an attempt to use HTTP/2.
|
|
// It is ignored in fetch.
|
|
ForceAttemptHTTP2 bool
|
|
}
|
|
|
|
// RoundTrip executes a single HTTP transaction, returning a Response for the provided Request.
|
|
// In the context of fetch, it will automatically follow redirects.
|
|
// This implementation is a wrapper around the fetch API.
|
|
func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) {
|
|
defer func() {
|
|
recovery := recover()
|
|
if recovery != nil {
|
|
runtimeErr, ok := recovery.(runtime.Error)
|
|
if ok {
|
|
err = runtimeErr
|
|
} else {
|
|
err = errors.New(recovery.(string))
|
|
}
|
|
}
|
|
}()
|
|
|
|
if req.GetBody != nil {
|
|
req.Body, err = req.GetBody()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
headersMapStringInterface := make(map[string]interface{})
|
|
for key, value := range req.Header {
|
|
headersMapStringInterface[key] = value
|
|
}
|
|
if req.Body != nil {
|
|
headersMapStringInterface["Content-Length"] = req.ContentLength
|
|
}
|
|
fetchArgs := map[string]interface{}{
|
|
"method": req.Method,
|
|
"headers": headersMapStringInterface,
|
|
}
|
|
// Since all supported browsers are chromium based, lets just detect for chrome.
|
|
// If we are, we can use a streamed client body
|
|
if req.Method != "GET" && req.Method != "HEAD" && req.Body != nil {
|
|
if !req.DisableStreamedClient {
|
|
if !js.Global().Get("chrome").IsUndefined() {
|
|
req.DisableStreamedClientChecks = true
|
|
}
|
|
}
|
|
var jsBody js.Value
|
|
if req.DisableStreamedClientChecks && !req.DisableStreamedClient {
|
|
jsBody = jsStreams.ReaderToReadableStream(req.Body)
|
|
fetchArgs["duplex"] = "half"
|
|
} else {
|
|
// Not today firefox, not today
|
|
// Mozilla, please add support for streamed client bodies
|
|
body, err := io.ReadAll(req.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
jsBody = js.Global().Get("Uint8Array").New(len(body))
|
|
js.CopyBytesToJS(jsBody, body)
|
|
}
|
|
fetchArgs["body"] = jsBody
|
|
}
|
|
promise := js.Global().Call("fetch", req.URL.String(), js.ValueOf(fetchArgs))
|
|
|
|
var waitGroup sync.WaitGroup
|
|
waitGroup.Add(1)
|
|
|
|
promise.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
resp = new(Response)
|
|
resp.Request = req
|
|
resp.Header = make(Header)
|
|
args[0].Get("headers").Call("forEach", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
resp.Header.Set(args[0].String(), args[1].String())
|
|
return nil
|
|
}))
|
|
resp.Status = args[0].Get("statusText").String()
|
|
resp.StatusCode = args[0].Get("status").Int()
|
|
if resp.Header.Has("Content-Length") {
|
|
resp.ContentLength, err = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
|
if err != nil {
|
|
resp.ContentLength = -1
|
|
}
|
|
}
|
|
resp.Body = jsStreams.NewReadableStream(args[0].Get("body"))
|
|
|
|
// Standard-library compatibility fields
|
|
resp.Proto = "HTTP/1.1"
|
|
resp.ProtoMajor = 1
|
|
resp.ProtoMinor = 1
|
|
resp.TransferEncoding = []string{"chunked"}
|
|
resp.Trailer = make(Header)
|
|
resp.TLS = nil
|
|
resp.Close = false
|
|
resp.Uncompressed = true
|
|
|
|
waitGroup.Done()
|
|
return nil
|
|
}))
|
|
|
|
promise.Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
err = errors.New(args[0].Get("message").String())
|
|
waitGroup.Done()
|
|
return nil
|
|
}))
|
|
|
|
waitGroup.Wait()
|
|
return
|
|
}
|
|
|
|
// Clone returns a copy of the Transport.
|
|
func (t *Transport) Clone() (newTransport *Transport) {
|
|
newTransport = new(Transport)
|
|
newTransport.Proxy = t.Proxy
|
|
newTransport.OnProxyError = t.OnProxyError
|
|
newTransport.DialContext = t.DialContext
|
|
newTransport.Dial = t.Dial
|
|
newTransport.DialTLSContext = t.DialTLSContext
|
|
newTransport.DialTLS = t.DialTLS
|
|
newTransport.TLSClientConfig = t.TLSClientConfig
|
|
newTransport.TLSHandshakeTimeout = t.TLSHandshakeTimeout
|
|
newTransport.DisableKeepAlives = t.DisableKeepAlives
|
|
newTransport.DisableCompression = t.DisableCompression
|
|
newTransport.MaxIdleConns = t.MaxIdleConns
|
|
newTransport.MaxIdleConnsPerHost = t.MaxIdleConnsPerHost
|
|
newTransport.IdleConnTimeout = t.IdleConnTimeout
|
|
newTransport.ResponseHeaderTimeout = t.ResponseHeaderTimeout
|
|
newTransport.ExpectContinueTimeout = t.ExpectContinueTimeout
|
|
newTransport.TLSNextProto = t.TLSNextProto
|
|
newTransport.ProxyConnectHeader = t.ProxyConnectHeader.Clone()
|
|
newTransport.MaxResponseHeaderBytes = t.MaxResponseHeaderBytes
|
|
newTransport.WriteBufferSize = t.WriteBufferSize
|
|
newTransport.ReadBufferSize = t.ReadBufferSize
|
|
newTransport.ForceAttemptHTTP2 = t.ForceAttemptHTTP2
|
|
return
|
|
}
|
|
|
|
// CloseIdleConnections closes any idle connections.
|
|
// This does nothing in fetch.
|
|
func (t *Transport) CloseIdleConnections() {}
|
|
|
|
// RegisterProtocol registers a new protocol with a custom RoundTripper.
|
|
// This does nothing in fetch.
|
|
func (t *Transport) RegisterProtocol(protocol string, rt RoundTripper) {}
|
|
|
|
// FetchRoundTripper is a wrapper around the fetch API. It is used to make requests.
|
|
// It is the default RoundTripper used for this subset of net/http.
|
|
var FetchRoundTripper RoundTripper = &Transport{}
|
|
|
|
// RoundTripper is an interface representing the ability to execute a single HTTP transaction.
|
|
type RoundTripper interface {
|
|
// RoundTrip executes a single HTTP transaction, returning a Response for the provided Request.
|
|
// In the context of fetch, it will automatically follow redirects.
|
|
RoundTrip(*Request) (*Response, error)
|
|
}
|
|
|
|
// Client is a fetch client. It is used to make requests.
|
|
type Client struct {
|
|
// Transport specifies the mechanism by which individual requests are made.
|
|
// If nil, FetchRoundTripper is used.
|
|
Transport RoundTripper
|
|
|
|
// Jar does nothing, but it is required for compatibility with the Go standard library.
|
|
Jar CookieJar
|
|
|
|
// Timeout specifies a time limit for requests made by this Client.
|
|
Timeout time.Duration
|
|
}
|
|
|
|
// Do sends an HTTP request and returns an HTTP response, following policy (such as redirects, cookies, auth) as configured on the client.
|
|
func (c *Client) Do(req *Request) (resp *Response, err error) {
|
|
if c.Transport == nil {
|
|
c.Transport = FetchRoundTripper
|
|
}
|
|
return c.Transport.RoundTrip(req)
|
|
}
|
|
|
|
// CookieJar does nothing, but it is required for compatibility with the Go standard library.
|
|
type CookieJar interface {
|
|
SetCookies(u *url.URL, cookies []*Cookie)
|
|
Cookies(u *url.URL) []*Cookie
|
|
}
|
|
|
|
// SameSite does nothing, but it is required for compatibility with the Go standard library.
|
|
type SameSite int
|
|
|
|
// Cookie does nothing, but it is required for compatibility with the Go standard library.
|
|
type Cookie struct {
|
|
Name string
|
|
Value string
|
|
Quoted bool
|
|
Path string
|
|
Domain string
|
|
Expires time.Time
|
|
RawExpires string
|
|
MaxAge int
|
|
Secure bool
|
|
HttpOnly bool
|
|
SameSite SameSite
|
|
Partitioned bool
|
|
Raw string
|
|
Unparsed []string
|
|
}
|
|
|
|
// Fetch is a fetch client. It is used to make requests.
|
|
// It wraps around JS fetch.
|
|
var Fetch = &Client{
|
|
Transport: FetchRoundTripper,
|
|
Timeout: 20 * time.Second,
|
|
}
|
|
|
|
func CanonicalHeaderKey(key string) string {
|
|
const allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~"
|
|
for _, char := range []rune(key) {
|
|
if !strings.Contains(allowedCharacters, string(char)) {
|
|
return key
|
|
}
|
|
}
|
|
splitStrings := strings.Split(key, "-")
|
|
for splitString, _ := range splitStrings {
|
|
var stringBuilder strings.Builder
|
|
stringBuilder.WriteRune(unicode.ToUpper([]rune(splitStrings[splitString])[0]))
|
|
stringBuilder.WriteString(strings.ToLower(splitStrings[splitString][1:]))
|
|
splitStrings[splitString] = stringBuilder.String()
|
|
}
|
|
return strings.Join(splitStrings, "-")
|
|
}
|
|
|
|
type Header map[string]string
|
|
|
|
func (h Header) Add(key, value string) {
|
|
h[CanonicalHeaderKey(key)] = value
|
|
}
|
|
|
|
func (h Header) Clone() (newHeader Header) {
|
|
maps.Copy(h, newHeader)
|
|
return
|
|
}
|
|
|
|
func (h Header) Del(key string) {
|
|
delete(h, CanonicalHeaderKey(key))
|
|
}
|
|
|
|
func (h Header) Get(key string) (value string) {
|
|
value, _ = h[CanonicalHeaderKey(key)]
|
|
return
|
|
}
|
|
|
|
func (h Header) Set(key, value string) {
|
|
h[CanonicalHeaderKey(key)] = value
|
|
}
|
|
|
|
func (h Header) Has(key string) (has bool) {
|
|
_, has = h[CanonicalHeaderKey(key)]
|
|
return
|
|
}
|
|
|
|
func (h Header) Values() (values []string) {
|
|
for _, value := range h {
|
|
values = append(values, value)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (h Header) Write(w io.Writer) error {
|
|
for key, value := range h {
|
|
_, err := w.Write([]byte(key + ": " + value + "\r\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
|
|
for key, value := range h {
|
|
excludeKey, _ := exclude[key]
|
|
if excludeKey {
|
|
continue
|
|
}
|
|
_, err := w.Write([]byte(key + ": " + value + "\r\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Request struct {
|
|
// DisableStreamedClient specifies whether to use a streamed client body.
|
|
// This is set to false by default, but has checks to make sure it's running on a supported browser.
|
|
// HTTP/2 or QUIC to be enabled when using V8, which is not always the case, particularly on
|
|
// older servers or test servers.
|
|
//
|
|
// If DisableStreamedClientChecks is set to true, the client will first attempt to detect if
|
|
// the server supports HTTP/2 or QUIC and if you are running a supported JavaScript engine.
|
|
// Supported browser engines include:
|
|
// - V8 (Chrome, Edge, Opera)
|
|
// Unsupported browser engines include:
|
|
// - SpiderMonkey (Firefox)
|
|
// - JavaScriptCore (Safari)
|
|
// - Chakra (Internet Explorer)
|
|
// - KJS (Konqueror)
|
|
// - Presto (Opera Mini and ancient versions of Opera)
|
|
// Data from https://caniuse.com/mdn-api_request_request_request_body_readablestream
|
|
// TL;DR If it's chromium it'll work.
|
|
DisableStreamedClient bool
|
|
|
|
// DisableStreamedClientChecks specifies whether to disable checks for streamed clients.
|
|
// It does nothing if UseStreamedClient is set to false.
|
|
// Having it set to false may add another HEAD request to the server, which may be undesirable.
|
|
// Also, forcing it on may be useful if SpiderMonkey one day supports streamed clients bodies.
|
|
DisableStreamedClientChecks bool
|
|
|
|
// Method specifies the HTTP method (GET, POST, PUT, etc.).
|
|
Method string
|
|
|
|
// URL specifies either the URL to fetch as a string, or a URL object.
|
|
URL *url.URL
|
|
|
|
// Headers is a Headers object, allowing you to set request headers.
|
|
Header Header
|
|
|
|
// Body is an optional body to be sent with the request.
|
|
Body io.ReadCloser
|
|
|
|
// GetBody is an optional function that returns a ReadCloser for the body.
|
|
GetBody func() (io.ReadCloser, error)
|
|
|
|
// ContentLength is the length of the body. It is mostly superseded by the Content-Length header.
|
|
ContentLength int64
|
|
|
|
// The following fields are not used by fetch and exist mostly for compatibility with the Go standard library.
|
|
// They will do nothing. Do not use them.
|
|
|
|
// Proto, ProtoMajor, ProtoMinor specify the HTTP protocol version.
|
|
// This is useless, as fetch does not allow you to specify the protocol version.
|
|
Proto string
|
|
ProtoMajor int
|
|
ProtoMinor int
|
|
|
|
// Close indicates whether to close the connection after the request.
|
|
// This is useless, as fetch does not allow you to specify whether to close the connection and instead forces you to use a WebSocket.
|
|
Close bool
|
|
|
|
// Host specifies the host to perform the request to.
|
|
// This is useless, as it only makes sense in the context of the Host or :authority headers, both of which are filled-in automatically by fetch.
|
|
Host string
|
|
|
|
// TransferEncoding specifies the transfer encodings to be used.
|
|
// This is useless, as since we always use a stream, the transfer encoding is always chunked.
|
|
TransferEncoding []string
|
|
|
|
// Form, PostForm, and MultipartForm specify the parsed form data.
|
|
// This is useless, because even the Go standard library does not use it.
|
|
// Use Body instead.
|
|
Form url.Values
|
|
PostForm url.Values
|
|
MultipartForm *url.Values
|
|
|
|
// Trailer specifies additional headers that are sent after the request body.
|
|
// This is useless, as fetch does not allow you to specify trailers.
|
|
// Not to mention, nothing supports trailers.
|
|
Trailer Header
|
|
|
|
// RemoteAddr and RequestURI specify the remote address and Request-URI for the request.
|
|
// This is useless, as fetch does not allow you to specify the remote address, and you should use URL instead of RequestURI.
|
|
RemoteAddr string
|
|
RequestURI string
|
|
|
|
// TLS allows you to specify the TLS connection state.
|
|
// This is useless, as fetch does not allow you to specify the TLS connection state.
|
|
TLS *tls.ConnectionState
|
|
|
|
// Cancel is an optional channel that can be used to cancel the request.
|
|
// This isn't even in the Go standard library anymore.
|
|
Cancel <-chan struct{}
|
|
|
|
// Response is the response that caused this request to be created, usually in a redirect.
|
|
// This is useless, as fetch follows redirects automatically.
|
|
Response *Response
|
|
|
|
// Pattern is the pattern that was matched for this request.
|
|
// This is useless, as fetch does not support pattern matching.
|
|
Pattern string
|
|
}
|
|
|
|
// AddCookie does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) AddCookie(c *Cookie) {}
|
|
|
|
// BasicAuth does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) BasicAuth() (username, password string, ok bool) { return }
|
|
|
|
func (r *Request) Clone() (newRequest *Request) {
|
|
newRequest = new(Request)
|
|
newRequest.Method = r.Method
|
|
newRequest.URL = r.URL
|
|
newRequest.Header = r.Header.Clone()
|
|
newRequest.Body = r.Body
|
|
newRequest.GetBody = r.GetBody
|
|
newRequest.ContentLength = r.ContentLength
|
|
newRequest.TransferEncoding = r.TransferEncoding
|
|
newRequest.Proto = r.Proto
|
|
newRequest.ProtoMajor = r.ProtoMajor
|
|
newRequest.ProtoMinor = r.ProtoMinor
|
|
newRequest.Close = r.Close
|
|
newRequest.Host = r.Host
|
|
newRequest.Form = r.Form
|
|
newRequest.PostForm = r.PostForm
|
|
newRequest.MultipartForm = r.MultipartForm
|
|
newRequest.Trailer = r.Trailer.Clone()
|
|
newRequest.RemoteAddr = r.RemoteAddr
|
|
newRequest.RequestURI = r.RequestURI
|
|
newRequest.TLS = r.TLS
|
|
newRequest.Cancel = r.Cancel
|
|
newRequest.Response = r.Response
|
|
newRequest.Pattern = r.Pattern
|
|
return
|
|
}
|
|
|
|
// Context does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) Context() context.Context { return context.Background() }
|
|
|
|
// Cookie does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) Cookie(name string) (*Cookie, error) { return nil, nil }
|
|
|
|
// Cookies does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) Cookies() []*Cookie { return nil }
|
|
|
|
// CookiesNamed does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) CookiesNamed(name string) []*Cookie { return nil }
|
|
|
|
// FormFile does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// FormValue does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) FormValue(key string) string { return "" }
|
|
|
|
// MultipartReader does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) MultipartReader() (*multipart.Reader, error) { return nil, nil }
|
|
|
|
// ParseForm does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) ParseForm() error { return nil }
|
|
|
|
// ParseMultipartForm does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) ParseMultipartForm(maxMemory int64) error { return nil }
|
|
|
|
// PostFormValue does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) PostFormValue(key string) string { return "" }
|
|
|
|
// ProtoAtLeast does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) ProtoAtLeast(major, minor int) bool { return false }
|
|
|
|
// Finally, something that does something!
|
|
|
|
// Referer returns the value of the Referer header.
|
|
func (r *Request) Referer() string {
|
|
return r.Header.Get("Referer")
|
|
}
|
|
|
|
// SetBasicAuth does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) SetBasicAuth(username, password string) {}
|
|
|
|
// UserAgent returns the value of the User-Agent header.
|
|
func (r *Request) UserAgent() string {
|
|
return r.Header.Get("User-Agent")
|
|
}
|
|
|
|
// WithContext does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) WithContext(ctx context.Context) *Request { return r }
|
|
|
|
// Write writes an HTTP/1.1 request, which is the header and body, in wire format.
|
|
// It is not yet implemented (and probably never will be, because after all, why would you need to write an HTTP request in wire format?).
|
|
func (r *Request) Write(w io.Writer) error { return errors.New("not implemented") }
|
|
|
|
// WriteProxy does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Request) WriteProxy(w io.Writer) error { return nil }
|
|
|
|
func NewRequest(method, uri string, body io.Reader) (request *Request, err error) {
|
|
request = new(Request)
|
|
request.Method = method
|
|
request.URL, err = url.Parse(uri)
|
|
if err != nil {
|
|
return
|
|
}
|
|
request.Header = make(Header)
|
|
request.Body = io.NopCloser(body)
|
|
return
|
|
}
|
|
|
|
type Response struct {
|
|
// Status specifies the HTTP status.
|
|
// It will be an empty string if using HTTP/2.
|
|
Status string
|
|
|
|
// StatusCode specifies the HTTP status code.
|
|
StatusCode int
|
|
|
|
// Header specifies the response headers.
|
|
Header Header
|
|
|
|
// Body is the response body.
|
|
Body io.ReadCloser
|
|
|
|
// TransferEncoding specifies the transfer encodings that have been applied to the response.
|
|
TransferEncoding []string
|
|
|
|
// ContentLength specifies the length of the body.
|
|
// It is mostly superseded by the Content-Length header.
|
|
// A value of -1 indicates that the length is unknown.
|
|
ContentLength int64
|
|
|
|
// Request is the request that was made to get this response.
|
|
Request *Request
|
|
|
|
// The following fields are not used by fetch and exist mostly for compatibility with the Go standard library.
|
|
// They will do nothing. Do not use them.
|
|
|
|
// Proto, ProtoMajor, ProtoMinor specify the HTTP protocol version.
|
|
// This is useless, as fetch does not allow you to specify the protocol version.
|
|
Proto string
|
|
ProtoMajor int
|
|
ProtoMinor int
|
|
|
|
// Close indicates whether to close the connection after the request.
|
|
// This is useless, as fetch does not allow you to specify whether to close the connection and instead forces you to use a WebSocket.
|
|
Close bool
|
|
|
|
// Uncompressed specifies whether the response is uncompressed.
|
|
// This is useless, as fetch does not allow you to specify whether the response is uncompressed.
|
|
Uncompressed bool
|
|
|
|
// Trailer specifies additional headers that are sent after the request body.
|
|
// This is useless, as fetch does not allow you to specify trailers.
|
|
// Not to mention, nothing supports trailers.
|
|
Trailer Header
|
|
|
|
// TLS allows you to specify the TLS connection state.
|
|
// This is useless, as fetch does not allow you to specify the TLS connection state.
|
|
TLS *tls.ConnectionState
|
|
}
|
|
|
|
func Get(url string) (response *Response, err error) {
|
|
request, err := NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
response, err = Fetch.Do(request)
|
|
return
|
|
}
|
|
|
|
func Post(url string, body io.Reader) (response *Response, err error) {
|
|
request, err := NewRequest("POST", url, body)
|
|
if err != nil {
|
|
return
|
|
}
|
|
response, err = Fetch.Do(request)
|
|
return
|
|
}
|
|
|
|
func PostForm(url string, data url.Values) (response *Response, err error) {
|
|
body := io.NopCloser(strings.NewReader(data.Encode()))
|
|
request, err := NewRequest("POST", url, body)
|
|
if err != nil {
|
|
return
|
|
}
|
|
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
response, err = Fetch.Do(request)
|
|
return
|
|
}
|
|
|
|
// Cookies does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Response) Cookies() []*Cookie { return nil }
|
|
|
|
// Location returns the location header (if present).
|
|
func (r *Response) Location() (string, error) {
|
|
if r.Header.Has("Location") {
|
|
return r.Header.Get("Location"), nil
|
|
} else {
|
|
return "", errors.New("http: no Location header in response")
|
|
}
|
|
}
|
|
|
|
// ProtoAtLeast does nothing, but it is required for compatibility with the Go standard library.
|
|
func (r *Response) ProtoAtLeast(major, minor int) bool { return true }
|
|
|
|
// Write writes an HTTP/1.1 response, which is the header and body, in wire format.
|
|
// It is not yet implemented (and probably never will be, because after all, why would you need to write an HTTP response in wire format?).
|
|
func (r *Response) Write(w io.Writer) error { return nil }
|