dt_automate/vendor/github.com/playwright-community/playwright-go/fetch.go
2025-02-19 18:30:19 +08:00

452 lines
12 KiB
Go

package playwright
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
)
type apiRequestImpl struct {
*Playwright
}
func (r *apiRequestImpl) NewContext(options ...APIRequestNewContextOptions) (APIRequestContext, error) {
overrides := map[string]interface{}{}
if len(options) == 1 {
if options[0].ClientCertificates != nil {
certs, err := transformClientCertificate(options[0].ClientCertificates)
if err != nil {
return nil, err
}
overrides["clientCertificates"] = certs
options[0].ClientCertificates = nil
}
if options[0].ExtraHttpHeaders != nil {
overrides["extraHTTPHeaders"] = serializeMapToNameAndValue(options[0].ExtraHttpHeaders)
options[0].ExtraHttpHeaders = nil
}
if options[0].StorageStatePath != nil {
var storageState *StorageState
storageString, err := os.ReadFile(*options[0].StorageStatePath)
if err != nil {
return nil, fmt.Errorf("could not read storage state file: %w", err)
}
err = json.Unmarshal(storageString, &storageState)
if err != nil {
return nil, fmt.Errorf("could not parse storage state file: %w", err)
}
options[0].StorageState = storageState
options[0].StorageStatePath = nil
}
}
channel, err := r.channel.Send("newRequest", options, overrides)
if err != nil {
return nil, err
}
return fromChannel(channel).(*apiRequestContextImpl), nil
}
func newApiRequestImpl(pw *Playwright) *apiRequestImpl {
return &apiRequestImpl{pw}
}
type apiRequestContextImpl struct {
channelOwner
tracing *tracingImpl
closeReason *string
}
func (r *apiRequestContextImpl) Dispose(options ...APIRequestContextDisposeOptions) error {
if len(options) == 1 {
r.closeReason = options[0].Reason
}
_, err := r.channel.Send("dispose", map[string]interface{}{
"reason": r.closeReason,
})
if errors.Is(err, ErrTargetClosed) {
return nil
}
return err
}
func (r *apiRequestContextImpl) Delete(url string, options ...APIRequestContextDeleteOptions) (APIResponse, error) {
opts := APIRequestContextFetchOptions{
Method: String("DELETE"),
}
if len(options) == 1 {
err := assignStructFields(&opts, options[0], false)
if err != nil {
return nil, err
}
}
return r.Fetch(url, opts)
}
func (r *apiRequestContextImpl) Fetch(urlOrRequest interface{}, options ...APIRequestContextFetchOptions) (APIResponse, error) {
switch v := urlOrRequest.(type) {
case string:
return r.innerFetch(v, nil, options...)
case Request:
return r.innerFetch("", v, options...)
default:
return nil, fmt.Errorf("urlOrRequest has unsupported type: %T", urlOrRequest)
}
}
func (r *apiRequestContextImpl) innerFetch(url string, request Request, options ...APIRequestContextFetchOptions) (APIResponse, error) {
if r.closeReason != nil {
return nil, fmt.Errorf("%w: %s", ErrTargetClosed, *r.closeReason)
}
overrides := map[string]interface{}{}
if url != "" {
overrides["url"] = url
} else if request != nil {
overrides["url"] = request.URL()
}
if len(options) == 1 {
if options[0].MaxRedirects != nil && *options[0].MaxRedirects < 0 {
return nil, errors.New("maxRedirects must be non-negative")
}
if options[0].MaxRetries != nil && *options[0].MaxRetries < 0 {
return nil, errors.New("maxRetries must be non-negative")
}
// only one of them can be specified
if countNonNil(options[0].Data, options[0].Form, options[0].Multipart) > 1 {
return nil, errors.New("only one of 'data', 'form' or 'multipart' can be specified")
}
if options[0].Method == nil {
if request != nil {
options[0].Method = String(request.Method())
} else {
options[0].Method = String("GET")
}
}
if options[0].Headers == nil {
if request != nil {
overrides["headers"] = serializeMapToNameAndValue(request.Headers())
}
} else {
overrides["headers"] = serializeMapToNameAndValue(options[0].Headers)
options[0].Headers = nil
}
if options[0].Data != nil {
switch v := options[0].Data.(type) {
case string:
headersArray, ok := overrides["headers"].([]map[string]string)
if ok && isJsonContentType(headersArray) {
if json.Valid([]byte(v)) {
overrides["jsonData"] = v
} else {
data, err := json.Marshal(v)
if err != nil {
return nil, fmt.Errorf("could not marshal data: %w", err)
}
overrides["jsonData"] = string(data)
}
} else {
overrides["postData"] = base64.StdEncoding.EncodeToString([]byte(v))
}
case []byte:
overrides["postData"] = base64.StdEncoding.EncodeToString(v)
case interface{}:
data, err := json.Marshal(v)
if err != nil {
return nil, fmt.Errorf("could not marshal data: %w", err)
}
overrides["jsonData"] = string(data)
default:
return nil, errors.New("data must be a string, []byte, or interface{} that can marshal to json")
}
options[0].Data = nil
} else if options[0].Form != nil {
form, ok := options[0].Form.(map[string]interface{})
if !ok {
return nil, errors.New("form must be a map")
}
overrides["formData"] = serializeMapToNameValue(form)
options[0].Form = nil
} else if options[0].Multipart != nil {
_, ok := options[0].Multipart.(map[string]interface{})
if !ok {
return nil, errors.New("multipart must be a map")
}
multipartData := []map[string]interface{}{}
for name, value := range options[0].Multipart.(map[string]interface{}) {
switch v := value.(type) {
case InputFile:
multipartData = append(multipartData, map[string]interface{}{
"name": name,
"file": map[string]string{
"name": v.Name,
"mimeType": v.MimeType,
"buffer": base64.StdEncoding.EncodeToString(v.Buffer),
},
})
default:
multipartData = append(multipartData, map[string]interface{}{
"name": name,
"value": String(fmt.Sprintf("%v", v)),
})
}
}
overrides["multipartData"] = multipartData
options[0].Multipart = nil
} else if request != nil {
postDataBuf, err := request.PostDataBuffer()
if err == nil {
overrides["postData"] = base64.StdEncoding.EncodeToString(postDataBuf)
}
}
if options[0].Params != nil {
overrides["params"] = serializeMapToNameValue(options[0].Params)
options[0].Params = nil
}
}
response, err := r.channel.Send("fetch", options, overrides)
if err != nil {
return nil, err
}
return newAPIResponse(r, response.(map[string]interface{})), nil
}
func (r *apiRequestContextImpl) Get(url string, options ...APIRequestContextGetOptions) (APIResponse, error) {
opts := APIRequestContextFetchOptions{
Method: String("GET"),
}
if len(options) == 1 {
err := assignStructFields(&opts, options[0], false)
if err != nil {
return nil, err
}
}
return r.Fetch(url, opts)
}
func (r *apiRequestContextImpl) Head(url string, options ...APIRequestContextHeadOptions) (APIResponse, error) {
opts := APIRequestContextFetchOptions{
Method: String("HEAD"),
}
if len(options) == 1 {
err := assignStructFields(&opts, options[0], false)
if err != nil {
return nil, err
}
}
return r.Fetch(url, opts)
}
func (r *apiRequestContextImpl) Patch(url string, options ...APIRequestContextPatchOptions) (APIResponse, error) {
opts := APIRequestContextFetchOptions{
Method: String("PATCH"),
}
if len(options) == 1 {
err := assignStructFields(&opts, options[0], false)
if err != nil {
return nil, err
}
}
return r.Fetch(url, opts)
}
func (r *apiRequestContextImpl) Put(url string, options ...APIRequestContextPutOptions) (APIResponse, error) {
opts := APIRequestContextFetchOptions{
Method: String("PUT"),
}
if len(options) == 1 {
err := assignStructFields(&opts, options[0], false)
if err != nil {
return nil, err
}
}
return r.Fetch(url, opts)
}
func (r *apiRequestContextImpl) Post(url string, options ...APIRequestContextPostOptions) (APIResponse, error) {
opts := APIRequestContextFetchOptions{
Method: String("POST"),
}
if len(options) == 1 {
err := assignStructFields(&opts, options[0], false)
if err != nil {
return nil, err
}
}
return r.Fetch(url, opts)
}
func (r *apiRequestContextImpl) StorageState(path ...string) (*StorageState, error) {
result, err := r.channel.SendReturnAsDict("storageState")
if err != nil {
return nil, err
}
if len(path) == 1 {
file, err := os.Create(path[0])
if err != nil {
return nil, err
}
if err := json.NewEncoder(file).Encode(result); err != nil {
return nil, err
}
if err := file.Close(); err != nil {
return nil, err
}
}
var storageState StorageState
remapMapToStruct(result, &storageState)
return &storageState, nil
}
func newAPIRequestContext(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *apiRequestContextImpl {
rc := &apiRequestContextImpl{}
rc.createChannelOwner(rc, parent, objectType, guid, initializer)
rc.tracing = fromChannel(initializer["tracing"]).(*tracingImpl)
return rc
}
type apiResponseImpl struct {
request *apiRequestContextImpl
initializer map[string]interface{}
headers *rawHeaders
}
func (r *apiResponseImpl) Body() ([]byte, error) {
result, err := r.request.channel.SendReturnAsDict("fetchResponseBody", []map[string]interface{}{
{
"fetchUid": r.fetchUid(),
},
})
if err != nil {
if errors.Is(err, ErrTargetClosed) {
return nil, errors.New("response has been disposed")
}
return nil, err
}
body := result["binary"]
if body == nil {
return nil, errors.New("response has been disposed")
}
return base64.StdEncoding.DecodeString(body.(string))
}
func (r *apiResponseImpl) Dispose() error {
_, err := r.request.channel.Send("disposeAPIResponse", []map[string]interface{}{
{
"fetchUid": r.fetchUid(),
},
})
return err
}
func (r *apiResponseImpl) Headers() map[string]string {
return r.headers.Headers()
}
func (r *apiResponseImpl) HeadersArray() []NameValue {
return r.headers.HeadersArray()
}
func (r *apiResponseImpl) JSON(v interface{}) error {
body, err := r.Body()
if err != nil {
return err
}
return json.Unmarshal(body, &v)
}
func (r *apiResponseImpl) Ok() bool {
return r.Status() == 0 || (r.Status() >= 200 && r.Status() <= 299)
}
func (r *apiResponseImpl) Status() int {
return int(r.initializer["status"].(float64))
}
func (r *apiResponseImpl) StatusText() string {
return r.initializer["statusText"].(string)
}
func (r *apiResponseImpl) Text() (string, error) {
body, err := r.Body()
if err != nil {
return "", err
}
return string(body), nil
}
func (r *apiResponseImpl) URL() string {
return r.initializer["url"].(string)
}
func (r *apiResponseImpl) fetchUid() string {
return r.initializer["fetchUid"].(string)
}
func (r *apiResponseImpl) fetchLog() ([]string, error) {
ret, err := r.request.channel.Send("fetchLog", map[string]interface{}{
"fetchUid": r.fetchUid(),
})
if err != nil {
return nil, err
}
result := make([]string, len(ret.([]interface{})))
for i, v := range ret.([]interface{}) {
result[i] = v.(string)
}
return result, nil
}
func newAPIResponse(context *apiRequestContextImpl, initializer map[string]interface{}) *apiResponseImpl {
return &apiResponseImpl{
request: context,
initializer: initializer,
headers: newRawHeaders(initializer["headers"]),
}
}
func countNonNil(args ...interface{}) int {
count := 0
for _, v := range args {
if v != nil {
count++
}
}
return count
}
func isJsonContentType(headers []map[string]string) bool {
if len(headers) > 0 {
for _, v := range headers {
if strings.ToLower(v["name"]) == "content-type" {
if v["value"] == "application/json" {
return true
}
}
}
}
return false
}
func serializeMapToNameValue(data map[string]interface{}) []map[string]string {
serialized := make([]map[string]string, 0, len(data))
for k, v := range data {
serialized = append(serialized, map[string]string{
"name": k,
"value": fmt.Sprintf("%v", v),
})
}
return serialized
}