github.com/MontFerret/ferret@v0.18.0/pkg/drivers/cdp/network/manager.go (about)

     1  package network
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  
     7  	"github.com/mafredri/cdp"
     8  	"github.com/mafredri/cdp/protocol/network"
     9  	"github.com/mafredri/cdp/protocol/page"
    10  	"github.com/pkg/errors"
    11  	"github.com/rs/zerolog"
    12  	"github.com/wI2L/jettison"
    13  
    14  	"github.com/MontFerret/ferret/pkg/drivers"
    15  	"github.com/MontFerret/ferret/pkg/drivers/cdp/events"
    16  	"github.com/MontFerret/ferret/pkg/runtime/core"
    17  	rtEvents "github.com/MontFerret/ferret/pkg/runtime/events"
    18  	"github.com/MontFerret/ferret/pkg/runtime/logging"
    19  	"github.com/MontFerret/ferret/pkg/runtime/values"
    20  )
    21  
    22  const BlankPageURL = "about:blank"
    23  
    24  type (
    25  	FrameLoadedListener = func(ctx context.Context, frame page.Frame)
    26  
    27  	Manager struct {
    28  		mu          sync.RWMutex
    29  		logger      zerolog.Logger
    30  		client      *cdp.Client
    31  		headers     *drivers.HTTPHeaders
    32  		loop        *events.Loop
    33  		interceptor *Interceptor
    34  		stop        context.CancelFunc
    35  		response    *sync.Map
    36  	}
    37  )
    38  
    39  func New(
    40  	logger zerolog.Logger,
    41  	client *cdp.Client,
    42  	options Options,
    43  ) (*Manager, error) {
    44  	ctx, cancel := context.WithCancel(context.Background())
    45  
    46  	m := new(Manager)
    47  	m.logger = logging.WithName(logger.With(), "network_manager").Logger()
    48  	m.client = client
    49  	m.headers = drivers.NewHTTPHeaders()
    50  	m.stop = cancel
    51  	m.response = new(sync.Map)
    52  
    53  	var err error
    54  
    55  	defer func() {
    56  		if err != nil {
    57  			m.stop()
    58  		}
    59  	}()
    60  
    61  	m.loop = events.NewLoop(
    62  		createResponseReceivedStreamFactory(client),
    63  	)
    64  
    65  	m.loop.AddListener(responseReceivedEvent, m.handleResponse)
    66  
    67  	if options.Filter != nil && len(options.Filter.Patterns) > 0 {
    68  		m.interceptor = NewInterceptor(logger, client)
    69  
    70  		if err := m.interceptor.AddFilter("resources", options.Filter); err != nil {
    71  			return nil, err
    72  		}
    73  
    74  		if err = m.interceptor.Run(ctx); err != nil {
    75  			return nil, err
    76  		}
    77  	}
    78  
    79  	if options.Cookies != nil && len(options.Cookies) > 0 {
    80  		for url, cookies := range options.Cookies {
    81  			err = m.setCookiesInternal(ctx, url, cookies)
    82  
    83  			if err != nil {
    84  				return nil, err
    85  			}
    86  		}
    87  	}
    88  
    89  	if options.Headers != nil && options.Headers.Length() > 0 {
    90  		err = m.setHeadersInternal(ctx, options.Headers)
    91  
    92  		if err != nil {
    93  			return nil, err
    94  		}
    95  	}
    96  
    97  	if err = m.loop.Run(ctx); err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	return m, nil
   102  }
   103  
   104  func (m *Manager) Close() error {
   105  	m.mu.Lock()
   106  	defer m.mu.Unlock()
   107  
   108  	m.logger.Trace().Msg("closing")
   109  
   110  	if m.stop != nil {
   111  		m.stop()
   112  		m.stop = nil
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  func (m *Manager) GetCookies(ctx context.Context) (*drivers.HTTPCookies, error) {
   119  	m.logger.Trace().Msg("starting to get cookies")
   120  
   121  	repl, err := m.client.Network.GetAllCookies(ctx)
   122  
   123  	if err != nil {
   124  		m.logger.Trace().Err(err).Msg("failed to get cookies")
   125  
   126  		return nil, errors.Wrap(err, "failed to get cookies")
   127  	}
   128  
   129  	cookies := drivers.NewHTTPCookies()
   130  
   131  	if repl.Cookies == nil {
   132  		m.logger.Trace().Msg("no cookies found")
   133  
   134  		return cookies, nil
   135  	}
   136  
   137  	for _, c := range repl.Cookies {
   138  		cookies.Set(toDriverCookie(c))
   139  	}
   140  
   141  	m.logger.Trace().Err(err).Msg("succeeded to get cookies")
   142  
   143  	return cookies, nil
   144  }
   145  
   146  func (m *Manager) SetCookies(ctx context.Context, url string, cookies *drivers.HTTPCookies) error {
   147  	m.mu.Lock()
   148  	defer m.mu.Unlock()
   149  
   150  	return m.setCookiesInternal(ctx, url, cookies)
   151  }
   152  
   153  func (m *Manager) setCookiesInternal(ctx context.Context, url string, cookies *drivers.HTTPCookies) error {
   154  	m.logger.Trace().Str("url", url).Msg("starting to set cookies")
   155  
   156  	if cookies == nil {
   157  		m.logger.Trace().Msg("nil cookies passed")
   158  
   159  		return errors.Wrap(core.ErrMissedArgument, "cookies")
   160  	}
   161  
   162  	if cookies.Length() == 0 {
   163  		m.logger.Trace().Msg("no cookies passed")
   164  
   165  		return nil
   166  	}
   167  
   168  	params := make([]network.CookieParam, 0, cookies.Length())
   169  
   170  	cookies.ForEach(func(value drivers.HTTPCookie, _ values.String) bool {
   171  		params = append(params, fromDriverCookie(url, value))
   172  
   173  		return true
   174  	})
   175  
   176  	err := m.client.Network.SetCookies(ctx, network.NewSetCookiesArgs(params))
   177  
   178  	if err != nil {
   179  		m.logger.Trace().Err(err).Msg("failed to set cookies")
   180  
   181  		return err
   182  	}
   183  
   184  	m.logger.Trace().Msg("succeeded to set cookies")
   185  
   186  	return nil
   187  }
   188  
   189  func (m *Manager) DeleteCookies(ctx context.Context, url string, cookies *drivers.HTTPCookies) error {
   190  	m.mu.Lock()
   191  	defer m.mu.Unlock()
   192  
   193  	m.logger.Trace().Str("url", url).Msg("starting to delete cookies")
   194  
   195  	if cookies == nil {
   196  		m.logger.Trace().Msg("nil cookies passed")
   197  
   198  		return errors.Wrap(core.ErrMissedArgument, "cookies")
   199  	}
   200  
   201  	if cookies.Length() == 0 {
   202  		m.logger.Trace().Msg("no cookies passed")
   203  
   204  		return nil
   205  	}
   206  
   207  	var err error
   208  
   209  	cookies.ForEach(func(value drivers.HTTPCookie, _ values.String) bool {
   210  		m.logger.Trace().Str("name", value.Name).Msg("deleting a cookie")
   211  
   212  		err = m.client.Network.DeleteCookies(ctx, fromDriverCookieDelete(url, value))
   213  
   214  		if err != nil {
   215  			m.logger.Trace().Err(err).Str("name", value.Name).Msg("failed to delete a cookie")
   216  
   217  			return false
   218  		}
   219  
   220  		m.logger.Trace().Str("name", value.Name).Msg("succeeded to delete a cookie")
   221  
   222  		return true
   223  	})
   224  
   225  	return err
   226  }
   227  
   228  func (m *Manager) GetHeaders(_ context.Context) (*drivers.HTTPHeaders, error) {
   229  	m.mu.Lock()
   230  	defer m.mu.Unlock()
   231  
   232  	if m.headers == nil {
   233  		return drivers.NewHTTPHeaders(), nil
   234  	}
   235  
   236  	return m.headers.Clone().(*drivers.HTTPHeaders), nil
   237  }
   238  
   239  func (m *Manager) SetHeaders(ctx context.Context, headers *drivers.HTTPHeaders) error {
   240  	m.mu.Lock()
   241  	defer m.mu.Unlock()
   242  
   243  	return m.setHeadersInternal(ctx, headers)
   244  }
   245  
   246  func (m *Manager) setHeadersInternal(ctx context.Context, headers *drivers.HTTPHeaders) error {
   247  	m.logger.Trace().Msg("starting to set headers")
   248  
   249  	if headers.Length() == 0 {
   250  		m.logger.Trace().Msg("no headers passed")
   251  
   252  		return nil
   253  	}
   254  
   255  	m.headers = headers
   256  
   257  	m.logger.Trace().Msg("marshaling headers")
   258  
   259  	j, err := jettison.MarshalOpts(headers, jettison.NoHTMLEscaping())
   260  
   261  	if err != nil {
   262  		m.logger.Trace().Err(err).Msg("failed to marshal headers")
   263  
   264  		return errors.Wrap(err, "failed to marshal headers")
   265  	}
   266  
   267  	m.logger.Trace().Msg("sending headers to browser")
   268  
   269  	err = m.client.Network.SetExtraHTTPHeaders(
   270  		ctx,
   271  		network.NewSetExtraHTTPHeadersArgs(j),
   272  	)
   273  
   274  	if err != nil {
   275  		m.logger.Trace().Err(err).Msg("failed to set headers")
   276  
   277  		return errors.Wrap(err, "failed to set headers")
   278  	}
   279  
   280  	m.logger.Trace().Msg("succeeded to set headers")
   281  
   282  	return nil
   283  }
   284  
   285  func (m *Manager) GetResponse(_ context.Context, frameID page.FrameID) (drivers.HTTPResponse, error) {
   286  	value, found := m.response.Load(frameID)
   287  
   288  	m.logger.Trace().
   289  		Str("frame_id", string(frameID)).
   290  		Bool("found", found).
   291  		Msg("getting frame response")
   292  
   293  	if !found {
   294  		return drivers.HTTPResponse{}, core.ErrNotFound
   295  	}
   296  
   297  	return *(value.(*drivers.HTTPResponse)), nil
   298  }
   299  
   300  func (m *Manager) Navigate(ctx context.Context, url values.String) error {
   301  	m.mu.Lock()
   302  	defer m.mu.Unlock()
   303  
   304  	if url == "" {
   305  		url = BlankPageURL
   306  	}
   307  
   308  	urlStr := url.String()
   309  	m.logger.Trace().Str("url", urlStr).Msg("starting navigation")
   310  
   311  	repl, err := m.client.Page.Navigate(ctx, page.NewNavigateArgs(urlStr))
   312  
   313  	if err == nil && repl.ErrorText != nil {
   314  		err = errors.New(*repl.ErrorText)
   315  	}
   316  
   317  	if err != nil {
   318  		m.logger.Trace().Err(err).Msg("failed starting navigation")
   319  
   320  		return err
   321  	}
   322  
   323  	m.logger.Trace().Msg("succeeded starting navigation")
   324  
   325  	return m.WaitForNavigation(ctx, WaitEventOptions{})
   326  }
   327  
   328  func (m *Manager) NavigateForward(ctx context.Context, skip values.Int) (values.Boolean, error) {
   329  	m.mu.Lock()
   330  	defer m.mu.Unlock()
   331  
   332  	m.logger.Trace().
   333  		Int64("skip", int64(skip)).
   334  		Msg("starting forward navigation")
   335  
   336  	history, err := m.client.Page.GetNavigationHistory(ctx)
   337  
   338  	if err != nil {
   339  		m.logger.Trace().
   340  			Err(err).
   341  			Msg("failed to get navigation history")
   342  
   343  		return values.False, err
   344  	}
   345  
   346  	length := len(history.Entries)
   347  	lastIndex := length - 1
   348  
   349  	// nowhere to go forward
   350  	if history.CurrentIndex == lastIndex {
   351  		m.logger.Trace().
   352  			Int("history_entries", length).
   353  			Int("history_current_index", history.CurrentIndex).
   354  			Int("history_last_index", lastIndex).
   355  			Msg("no forward history. nowhere to navigate. done.")
   356  
   357  		return values.False, nil
   358  	}
   359  
   360  	if skip < 1 {
   361  		skip = 1
   362  	}
   363  
   364  	to := int(skip) + history.CurrentIndex
   365  
   366  	if to > lastIndex {
   367  		m.logger.Trace().
   368  			Int("history_entries", length).
   369  			Int("history_current_index", history.CurrentIndex).
   370  			Int("history_last_index", lastIndex).
   371  			Int("history_target_index", to).
   372  			Msg("not enough history items. using the edge index")
   373  
   374  		to = lastIndex
   375  	}
   376  
   377  	entry := history.Entries[to]
   378  	err = m.client.Page.NavigateToHistoryEntry(ctx, page.NewNavigateToHistoryEntryArgs(entry.ID))
   379  
   380  	if err != nil {
   381  		m.logger.Trace().
   382  			Int("history_entries", length).
   383  			Int("history_current_index", history.CurrentIndex).
   384  			Int("history_last_index", lastIndex).
   385  			Int("history_target_index", to).
   386  			Err(err).
   387  			Msg("failed to get navigation history entry")
   388  
   389  		return values.False, err
   390  	}
   391  
   392  	err = m.WaitForNavigation(ctx, WaitEventOptions{})
   393  
   394  	if err != nil {
   395  		m.logger.Trace().
   396  			Int("history_entries", length).
   397  			Int("history_current_index", history.CurrentIndex).
   398  			Int("history_last_index", lastIndex).
   399  			Int("history_target_index", to).
   400  			Err(err).
   401  			Msg("failed to wait for navigation completion")
   402  
   403  		return values.False, err
   404  	}
   405  
   406  	m.logger.Trace().
   407  		Int("history_entries", length).
   408  		Int("history_current_index", history.CurrentIndex).
   409  		Int("history_last_index", lastIndex).
   410  		Int("history_target_index", to).
   411  		Msg("succeeded to wait for navigation completion")
   412  
   413  	return values.True, nil
   414  }
   415  
   416  func (m *Manager) NavigateBack(ctx context.Context, skip values.Int) (values.Boolean, error) {
   417  	m.mu.Lock()
   418  	defer m.mu.Unlock()
   419  
   420  	m.logger.Trace().
   421  		Int64("skip", int64(skip)).
   422  		Msg("starting backward navigation")
   423  
   424  	history, err := m.client.Page.GetNavigationHistory(ctx)
   425  
   426  	if err != nil {
   427  		m.logger.Trace().Err(err).Msg("failed to get navigation history")
   428  
   429  		return values.False, err
   430  	}
   431  
   432  	length := len(history.Entries)
   433  
   434  	// we are in the beginning
   435  	if history.CurrentIndex == 0 {
   436  		m.logger.Trace().
   437  			Int("history_entries", length).
   438  			Int("history_current_index", history.CurrentIndex).
   439  			Msg("no backward history. nowhere to navigate. done.")
   440  
   441  		return values.False, nil
   442  	}
   443  
   444  	if skip < 1 {
   445  		skip = 1
   446  	}
   447  
   448  	to := history.CurrentIndex - int(skip)
   449  
   450  	if to < 0 {
   451  		m.logger.Trace().
   452  			Int("history_entries", length).
   453  			Int("history_current_index", history.CurrentIndex).
   454  			Int("history_target_index", to).
   455  			Msg("not enough history items. using 0 index")
   456  
   457  		to = 0
   458  	}
   459  
   460  	entry := history.Entries[to]
   461  	err = m.client.Page.NavigateToHistoryEntry(ctx, page.NewNavigateToHistoryEntryArgs(entry.ID))
   462  
   463  	if err != nil {
   464  		m.logger.Trace().
   465  			Int("history_entries", length).
   466  			Int("history_current_index", history.CurrentIndex).
   467  			Int("history_target_index", to).
   468  			Err(err).
   469  			Msg("failed to get navigation history entry")
   470  
   471  		return values.False, err
   472  	}
   473  
   474  	err = m.WaitForNavigation(ctx, WaitEventOptions{})
   475  
   476  	if err != nil {
   477  		m.logger.Trace().
   478  			Int("history_entries", length).
   479  			Int("history_current_index", history.CurrentIndex).
   480  			Int("history_target_index", to).
   481  			Err(err).
   482  			Msg("failed to wait for navigation completion")
   483  
   484  		return values.False, err
   485  	}
   486  
   487  	m.logger.Trace().
   488  		Int("history_entries", length).
   489  		Int("history_current_index", history.CurrentIndex).
   490  		Int("history_target_index", to).
   491  		Msg("succeeded to wait for navigation completion")
   492  
   493  	return values.True, nil
   494  }
   495  
   496  func (m *Manager) WaitForNavigation(ctx context.Context, opts WaitEventOptions) error {
   497  	stream, err := m.OnNavigation(ctx)
   498  	if err != nil {
   499  		return err
   500  	}
   501  
   502  	defer stream.Close(ctx)
   503  
   504  	ctx, cancel := context.WithCancel(ctx)
   505  	defer cancel()
   506  
   507  	for evt := range stream.Read(ctx) {
   508  		if err := ctx.Err(); err != nil {
   509  			return err
   510  		}
   511  
   512  		if err := evt.Err(); err != nil {
   513  			return nil
   514  		}
   515  
   516  		nav := evt.Value().(*NavigationEvent)
   517  
   518  		if !isFrameMatched(nav.FrameID, opts.FrameID) || !isURLMatched(nav.URL, opts.URL) {
   519  			continue
   520  		}
   521  
   522  		return nil
   523  	}
   524  
   525  	return nil
   526  }
   527  
   528  func (m *Manager) OnNavigation(ctx context.Context) (rtEvents.Stream, error) {
   529  	m.logger.Trace().Msg("starting to wait for frame navigation event")
   530  
   531  	stream1, err := m.client.Page.FrameNavigated(ctx)
   532  
   533  	if err != nil {
   534  		m.logger.Trace().Err(err).Msg("failed to open frame navigation event stream")
   535  
   536  		return nil, err
   537  	}
   538  
   539  	stream2, err := m.client.Page.NavigatedWithinDocument(ctx)
   540  
   541  	if err != nil {
   542  		_ = stream1.Close()
   543  		m.logger.Trace().Err(err).Msg("failed to open within document navigation event streams")
   544  
   545  		return nil, err
   546  	}
   547  
   548  	return newNavigationEventStream(m.logger, m.client, stream1, stream2), nil
   549  }
   550  
   551  func (m *Manager) OnRequest(ctx context.Context) (rtEvents.Stream, error) {
   552  	m.logger.Trace().Msg("starting to receive request event")
   553  
   554  	stream, err := m.client.Network.RequestWillBeSent(ctx)
   555  
   556  	if err != nil {
   557  		m.logger.Trace().Err(err).Msg("failed to open request event stream")
   558  
   559  		return nil, err
   560  	}
   561  
   562  	m.logger.Trace().Msg("succeeded to receive request event")
   563  
   564  	return newRequestWillBeSentStream(m.logger, stream), nil
   565  }
   566  
   567  func (m *Manager) OnResponse(ctx context.Context) (rtEvents.Stream, error) {
   568  	m.logger.Trace().Msg("starting to receive response events")
   569  
   570  	stream, err := m.client.Network.ResponseReceived(ctx)
   571  
   572  	if err != nil {
   573  		m.logger.Trace().Err(err).Msg("failed to open response event stream")
   574  
   575  		return nil, err
   576  	}
   577  
   578  	m.logger.Trace().Msg("succeeded to receive response events")
   579  
   580  	return newResponseReceivedReader(m.logger, m.client, stream), nil
   581  }
   582  
   583  func (m *Manager) handleResponse(_ context.Context, message interface{}) (out bool) {
   584  	out = true
   585  	msg, ok := message.(*network.ResponseReceivedReply)
   586  
   587  	if !ok {
   588  		return
   589  	}
   590  
   591  	// we are interested in documents only
   592  	if msg.Type != network.ResourceTypeDocument {
   593  		return
   594  	}
   595  
   596  	if msg.FrameID == nil {
   597  		return
   598  	}
   599  
   600  	log := m.logger.With().
   601  		Str("frame_id", string(*msg.FrameID)).
   602  		Str("request_id", string(msg.RequestID)).
   603  		Str("loader_id", string(msg.LoaderID)).
   604  		Float64("timestamp", float64(msg.Timestamp)).
   605  		Str("url", msg.Response.URL).
   606  		Int("status_code", msg.Response.Status).
   607  		Str("status_text", msg.Response.StatusText).
   608  		Logger()
   609  
   610  	log.Trace().Msg("received browser response")
   611  
   612  	m.response.Store(*msg.FrameID, toDriverResponse(msg.Response, nil))
   613  
   614  	log.Trace().Msg("updated frame response information")
   615  
   616  	return
   617  }