271 lines
6.4 KiB
Go
271 lines
6.4 KiB
Go
package playwright
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type routeImpl struct {
|
|
channelOwner
|
|
handling *chan bool
|
|
context *browserContextImpl
|
|
didThrow bool
|
|
}
|
|
|
|
func (r *routeImpl) startHandling() chan bool {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
handling := make(chan bool, 1)
|
|
r.handling = &handling
|
|
return handling
|
|
}
|
|
|
|
func (r *routeImpl) reportHandled(done bool) {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
if r.handling != nil {
|
|
handling := *r.handling
|
|
r.handling = nil
|
|
handling <- done
|
|
}
|
|
}
|
|
|
|
func (r *routeImpl) checkNotHandled() error {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
if r.handling == nil {
|
|
return errors.New("Route is already handled!")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *routeImpl) Request() Request {
|
|
return fromChannel(r.initializer["request"]).(*requestImpl)
|
|
}
|
|
|
|
func unpackOptionalArgument(input interface{}) interface{} {
|
|
inputValue := reflect.ValueOf(input)
|
|
if inputValue.Kind() != reflect.Slice {
|
|
panic("Needs to be a slice")
|
|
}
|
|
if inputValue.Len() == 0 {
|
|
return nil
|
|
}
|
|
return inputValue.Index(0).Interface()
|
|
}
|
|
|
|
func (r *routeImpl) Abort(errorCode ...string) error {
|
|
return r.handleRoute(func() error {
|
|
return r.raceWithPageClose(func() error {
|
|
_, err := r.channel.Send("abort", map[string]interface{}{
|
|
"errorCode": unpackOptionalArgument(errorCode),
|
|
})
|
|
return err
|
|
})
|
|
})
|
|
}
|
|
|
|
func (r *routeImpl) raceWithPageClose(f func() error) error {
|
|
errChan := make(chan error, 1)
|
|
go func() {
|
|
errChan <- f()
|
|
}()
|
|
|
|
select {
|
|
case <-r.Request().(*requestImpl).targetClosed():
|
|
// upstream does not throw the err
|
|
return nil
|
|
case err := <-errChan:
|
|
return err
|
|
}
|
|
}
|
|
|
|
func (r *routeImpl) Fulfill(options ...RouteFulfillOptions) error {
|
|
return r.handleRoute(func() error {
|
|
return r.innerFulfill(options...)
|
|
})
|
|
}
|
|
|
|
func (r *routeImpl) handleRoute(cb func() error) error {
|
|
err := r.checkNotHandled()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cb(); err != nil {
|
|
r.didThrow = true
|
|
return err
|
|
}
|
|
r.reportHandled(true)
|
|
return nil
|
|
}
|
|
|
|
func (r *routeImpl) innerFulfill(options ...RouteFulfillOptions) error {
|
|
err := r.checkNotHandled()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
option := RouteFulfillOptions{}
|
|
if len(options) == 1 {
|
|
option = options[0]
|
|
}
|
|
overrides := map[string]interface{}{
|
|
"status": 200,
|
|
}
|
|
headers := make(map[string]string)
|
|
|
|
if option.Response != nil {
|
|
overrides["status"] = option.Response.Status()
|
|
headers = option.Response.Headers()
|
|
response, ok := option.Response.(*apiResponseImpl)
|
|
if option.Body == nil && option.Path == nil && ok && response.request.connection == r.connection {
|
|
overrides["fetchResponseUid"] = response.fetchUid()
|
|
} else {
|
|
option.Body, _ = option.Response.Body()
|
|
}
|
|
option.Response = nil
|
|
}
|
|
if option.Status != nil {
|
|
overrides["status"] = *option.Status
|
|
option.Status = nil
|
|
}
|
|
|
|
length := 0
|
|
isBase64 := false
|
|
var fileContentType string
|
|
if _, ok := option.Body.(string); ok {
|
|
isBase64 = false
|
|
} else if body, ok := option.Body.([]byte); ok {
|
|
option.Body = base64.StdEncoding.EncodeToString(body)
|
|
length = len(body)
|
|
isBase64 = true
|
|
} else if option.Path != nil {
|
|
content, err := os.ReadFile(*option.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fileContentType = http.DetectContentType(content)
|
|
option.Body = base64.StdEncoding.EncodeToString(content)
|
|
isBase64 = true
|
|
length = len(content)
|
|
}
|
|
|
|
if option.Headers != nil {
|
|
headers = make(map[string]string)
|
|
for key, val := range option.Headers {
|
|
headers[strings.ToLower(key)] = val
|
|
}
|
|
option.Headers = nil
|
|
}
|
|
if option.ContentType != nil {
|
|
headers["content-type"] = *option.ContentType
|
|
option.ContentType = nil
|
|
} else if option.Path != nil {
|
|
headers["content-type"] = fileContentType
|
|
}
|
|
if _, ok := headers["content-length"]; !ok && length > 0 {
|
|
headers["content-length"] = strconv.Itoa(length)
|
|
}
|
|
overrides["headers"] = serializeMapToNameAndValue(headers)
|
|
overrides["isBase64"] = isBase64
|
|
|
|
option.Path = nil
|
|
err = r.raceWithPageClose(func() error {
|
|
_, err := r.channel.Send("fulfill", option, overrides)
|
|
return err
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (r *routeImpl) Fallback(options ...RouteFallbackOptions) error {
|
|
err := r.checkNotHandled()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opt := RouteFallbackOptions{}
|
|
if len(options) == 1 {
|
|
opt = options[0]
|
|
}
|
|
r.Request().(*requestImpl).applyFallbackOverrides(opt)
|
|
r.reportHandled(false)
|
|
return nil
|
|
}
|
|
|
|
func (r *routeImpl) Fetch(options ...RouteFetchOptions) (APIResponse, error) {
|
|
opt := &APIRequestContextFetchOptions{}
|
|
url := ""
|
|
if len(options) == 1 {
|
|
err := assignStructFields(opt, options[0], true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opt.Data = options[0].PostData
|
|
if options[0].URL != nil {
|
|
url = *options[0].URL
|
|
}
|
|
}
|
|
ret, err := r.connection.WrapAPICall(func() (interface{}, error) {
|
|
return r.context.request.innerFetch(url, r.Request(), *opt)
|
|
}, false)
|
|
if ret == nil {
|
|
return nil, err
|
|
}
|
|
return ret.(APIResponse), err
|
|
}
|
|
|
|
func (r *routeImpl) Continue(options ...RouteContinueOptions) error {
|
|
option := &RouteFallbackOptions{}
|
|
if len(options) == 1 {
|
|
err := assignStructFields(option, options[0], true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return r.handleRoute(func() error {
|
|
r.Request().(*requestImpl).applyFallbackOverrides(*option)
|
|
return r.internalContinue(false)
|
|
})
|
|
}
|
|
|
|
func (r *routeImpl) internalContinue(isFallback bool) error {
|
|
overrides := make(map[string]interface{})
|
|
overrides["url"] = r.Request().(*requestImpl).fallbackOverrides.URL
|
|
overrides["method"] = r.Request().(*requestImpl).fallbackOverrides.Method
|
|
headers := r.Request().(*requestImpl).fallbackOverrides.Headers
|
|
if headers != nil {
|
|
overrides["headers"] = serializeMapToNameAndValue(headers)
|
|
}
|
|
postDataBuf := r.Request().(*requestImpl).fallbackOverrides.PostDataBuffer
|
|
if postDataBuf != nil {
|
|
overrides["postData"] = base64.StdEncoding.EncodeToString(postDataBuf)
|
|
}
|
|
overrides["isFallback"] = isFallback
|
|
return r.raceWithPageClose(func() error {
|
|
_, err := r.channel.Send("continue", overrides)
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (r *routeImpl) redirectedNavigationRequest(url string) error {
|
|
return r.handleRoute(func() error {
|
|
return r.raceWithPageClose(func() error {
|
|
_, err := r.channel.Send("redirectNavigationRequest", map[string]interface{}{
|
|
"url": url,
|
|
})
|
|
return err
|
|
})
|
|
})
|
|
}
|
|
|
|
func newRoute(parent *channelOwner, objectType string, guid string, initializer map[string]interface{}) *routeImpl {
|
|
bt := &routeImpl{}
|
|
bt.createChannelOwner(bt, parent, objectType, guid, initializer)
|
|
bt.markAsInternalType()
|
|
return bt
|
|
}
|