forked from SeleniumHQ/selenium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNetworkManager.cs
273 lines (240 loc) · 11.6 KB
/
NetworkManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// <copyright file="NetworkManager.cs" company="WebDriver Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using OpenQA.Selenium.DevTools;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace OpenQA.Selenium
{
/// <summary>
/// Provides methods for monitoring, intercepting, and modifying network requests and responses.
/// </summary>
public class NetworkManager : INetwork
{
private Lazy<DevToolsSession> session;
private List<NetworkRequestHandler> requestHandlers = new List<NetworkRequestHandler>();
private List<NetworkResponseHandler> responseHandlers = new List<NetworkResponseHandler>();
private List<NetworkAuthenticationHandler> authenticationHandlers = new List<NetworkAuthenticationHandler>();
/// <summary>
/// Initializes a new instance of the <see cref="NetworkManager"/> class.
/// </summary>
/// <param name="driver">The <see cref="IWebDriver"/> instance on which the network should be monitored.</param>
public NetworkManager(IWebDriver driver)
{
// Use of Lazy<T> means this exception won't be thrown until the user first
// attempts to access the DevTools session, probably on the first call to
// StartMonitoring().
this.session = new Lazy<DevToolsSession>(() =>
{
IDevTools devToolsDriver = driver as IDevTools;
if (devToolsDriver == null)
{
throw new WebDriverException("Driver must implement IDevTools to use these features");
}
return devToolsDriver.GetDevToolsSession();
});
}
/// <summary>
/// Occurs when a browser sends a network request.
/// </summary>
public event EventHandler<NetworkRequestSentEventArgs> NetworkRequestSent;
/// <summary>
/// Occurs when a browser receives a network response.
/// </summary>
public event EventHandler<NetworkResponseReceivedEventArgs> NetworkResponseReceived;
/// <summary>
/// Asynchronously starts monitoring for network traffic.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task StartMonitoring()
{
this.session.Value.Domains.Network.RequestPaused += OnRequestPaused;
this.session.Value.Domains.Network.AuthRequired += OnAuthRequired;
this.session.Value.Domains.Network.ResponsePaused += OnResponsePaused;
await this.session.Value.Domains.Network.EnableFetchForAllPatterns().ConfigureAwait(false);
await this.session.Value.Domains.Network.EnableNetwork().ConfigureAwait(false);
await this.session.Value.Domains.Network.DisableNetworkCaching().ConfigureAwait(false);
}
/// <summary>
/// Asynchronously stops monitoring for network traffic.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task StopMonitoring()
{
this.session.Value.Domains.Network.ResponsePaused -= OnResponsePaused;
this.session.Value.Domains.Network.AuthRequired -= OnAuthRequired;
this.session.Value.Domains.Network.RequestPaused -= OnRequestPaused;
await this.session.Value.Domains.Network.EnableNetworkCaching().ConfigureAwait(false);
}
/// <summary>
/// Adds a <see cref="NetworkRequestHandler"/> to examine incoming network requests,
/// and optionally modify the request or provide a response.
/// </summary>
/// <param name="handler">The <see cref="NetworkRequestHandler"/> to add.</param>
public void AddRequestHandler(NetworkRequestHandler handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler), "Request handler cannot be null");
}
if (handler.RequestMatcher == null)
{
throw new ArgumentException("Matcher for request cannot be null", nameof(handler));
}
if (handler.RequestTransformer == null && handler.ResponseSupplier == null)
{
throw new ArgumentException("Request transformer and response supplier cannot both be null", nameof(handler));
}
this.requestHandlers.Add(handler);
}
/// <summary>
/// Clears all added <see cref="NetworkRequestHandler"/> instances.
/// </summary>
public void ClearRequestHandlers()
{
this.requestHandlers.Clear();
}
/// <summary>
/// Adds a <see cref="NetworkAuthenticationHandler"/> to supply authentication
/// credentials for network requests.
/// </summary>
/// <param name="handler">The <see cref="NetworkAuthenticationHandler"/> to add.</param>
public void AddAuthenticationHandler(NetworkAuthenticationHandler handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler), "Authentication handler cannot be null");
}
if (handler.UriMatcher == null)
{
throw new ArgumentException("Matcher for delegate for URL cannot be null", nameof(handler));
}
if (handler.Credentials == null)
{
throw new ArgumentException("Credentials to use for authentication cannot be null", nameof(handler));
}
var passwordCredentials = handler.Credentials as PasswordCredentials;
if (passwordCredentials == null)
{
throw new ArgumentException("Credentials must contain user name and password (PasswordCredentials)", nameof(handler));
}
this.authenticationHandlers.Add(handler);
}
/// <summary>
/// Clears all added <see cref="NetworkAuthenticationHandler"/> instances.
/// </summary>
public void ClearAuthenticationHandlers()
{
this.authenticationHandlers.Clear();
}
/// <summary>
/// Adds a <see cref="NetworkResponseHandler"/> to examine received network responses,
/// and optionally modify the response.
/// </summary>
/// <param name="handler">The <see cref="NetworkResponseHandler"/> to add.</param>
public void AddResponseHandler(NetworkResponseHandler handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler), "Request handler cannot be null");
}
if (handler.ResponseMatcher == null)
{
throw new ArgumentException("Matcher for response cannot be null", nameof(handler));
}
this.responseHandlers.Add(handler);
}
/// <summary>
/// Clears all added <see cref="NetworkResponseHandler"/> instances.
/// </summary>
public void ClearResponseHandlers()
{
this.responseHandlers.Clear();
}
private async Task OnAuthRequired(object sender, AuthRequiredEventArgs e)
{
string requestId = e.RequestId;
Uri uri = new Uri(e.Uri);
bool successfullyAuthenticated = false;
foreach (var authenticationHandler in this.authenticationHandlers)
{
if (authenticationHandler.UriMatcher.Invoke(uri))
{
PasswordCredentials credentials = authenticationHandler.Credentials as PasswordCredentials;
await this.session.Value.Domains.Network.ContinueWithAuth(e.RequestId, credentials.UserName, credentials.Password).ConfigureAwait(false);
successfullyAuthenticated = true;
break;
}
}
if (!successfullyAuthenticated)
{
await this.session.Value.Domains.Network.CancelAuth(e.RequestId).ConfigureAwait(false);
}
}
private async Task OnRequestPaused(object sender, RequestPausedEventArgs e)
{
if (this.NetworkRequestSent != null)
{
this.NetworkRequestSent(this, new NetworkRequestSentEventArgs(e.RequestData));
}
foreach (var handler in this.requestHandlers)
{
if (handler.RequestMatcher.Invoke(e.RequestData))
{
if (handler.RequestTransformer != null)
{
await this.session.Value.Domains.Network.ContinueRequest(handler.RequestTransformer(e.RequestData)).ConfigureAwait(false);
return;
}
if (handler.ResponseSupplier != null)
{
await this.session.Value.Domains.Network.ContinueRequestWithResponse(e.RequestData, handler.ResponseSupplier(e.RequestData)).ConfigureAwait(false);
return;
}
}
}
await this.session.Value.Domains.Network.ContinueRequestWithoutModification(e.RequestData).ConfigureAwait(false);
}
private async Task OnResponsePaused(object sender, ResponsePausedEventArgs e)
{
if (e.ResponseData.Headers.Count > 0)
{
// If no headers are present, the body cannot be retrieved.
await this.session.Value.Domains.Network.AddResponseBody(e.ResponseData).ConfigureAwait(false);
}
if (this.NetworkResponseReceived != null)
{
this.NetworkResponseReceived(this, new NetworkResponseReceivedEventArgs(e.ResponseData));
}
foreach (var handler in this.responseHandlers)
{
if (handler.ResponseMatcher.Invoke(e.ResponseData))
{
// NOTE: We create a dummy HttpRequestData object here, because the ContinueRequestWithResponse
// method demands one; however, the only property used by that method is the RequestId property.
// It might be better to refactor that method signature to simply pass the request ID, or
// alternatively, just pass the response data, which should also contain the request ID anyway.
HttpRequestData requestData = new HttpRequestData() { RequestId = e.ResponseData.RequestId };
await this.session.Value.Domains.Network.ContinueRequestWithResponse(requestData, handler.ResponseTransformer(e.ResponseData)).ConfigureAwait(false);
return;
}
}
await this.session.Value.Domains.Network.ContinueResponseWithoutModification(e.ResponseData).ConfigureAwait(false);
}
}
}