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 }