Remove duplication between request/response properties in NetworkEventActor (#37651)

- Remove request/response fields in `NetworkEventActor` which now stores
minimal request metadata needed : `URL`, `method`, `timestamps`, `raw
headers`, `body`.
- Refactor add_request and add_response: `add_request` takes the
incoming DevtoolsHttpRequest and populates
`request_url`, `request_method`, `request_started`,
`request_time_stamp`, `request_body`, `request_headers_raw`
`request_cookies`, `request_headers`,`total_time` and `event_timing`
(via a new helper that computes Timings)
While `add_response` takes the incoming DevtoolsHttpResponse and
populates `response_headers_raw`, `response_body` ,`response_cookies`,
`response_headers`, `response_start`, `response_content`
- Add a call to `resources_updated` to push initial request info in
`handle_network_event`
Testing: Run and logged servo in devtools mode and now, the request
header info isavailable as soon as the request gets initiated
Fixes: https://github.com/servo/servo/issues/37566

---------

Signed-off-by: Uthman Yahaya Baba <uthmanyahayababa@gmail.com>
This commit is contained in:
Usman Yahaya Baba 2025-06-25 21:18:44 +01:00 committed by GitHub
parent 459397e1a1
commit 152eb63fb3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 125 additions and 136 deletions

View file

@ -12,7 +12,6 @@ use chrono::{Local, LocalResult, TimeZone};
use devtools_traits::{HttpRequest as DevtoolsHttpRequest, HttpResponse as DevtoolsHttpResponse};
use headers::{ContentType, Cookie, HeaderMapExt};
use http::{HeaderMap, Method, header};
use net_traits::http_status::HttpStatus;
use serde::Serialize;
use serde_json::{Map, Value};
@ -21,36 +20,23 @@ use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::network_handler::Cause;
use crate::protocol::JsonPacketStream;
#[derive(Clone)]
pub struct HttpRequest {
url: String,
method: Method,
headers: HeaderMap,
body: Option<Vec<u8>>,
started_date_time: SystemTime,
time_stamp: i64,
connect_time: Duration,
send_time: Duration,
}
#[derive(Clone)]
pub struct HttpResponse {
headers: Option<HeaderMap>,
status: HttpStatus,
body: Option<Vec<u8>>,
}
pub struct NetworkEventActor {
pub name: String,
pub request: HttpRequest,
pub response: HttpResponse,
pub is_xhr: bool,
pub request_url: String,
pub request_method: Method,
pub request_started: SystemTime,
pub request_time_stamp: i64,
pub request_headers_raw: Option<HeaderMap>,
pub request_body: Option<Vec<u8>>,
pub request_cookies: Option<RequestCookiesMsg>,
pub request_headers: Option<RequestHeadersMsg>,
pub response_headers_raw: Option<HeaderMap>,
pub response_body: Option<Vec<u8>>,
pub response_content: Option<ResponseContentMsg>,
pub response_start: Option<ResponseStartMsg>,
pub response_cookies: Option<ResponseCookiesMsg>,
pub response_headers: Option<ResponseHeadersMsg>,
pub request_cookies: Option<RequestCookiesMsg>,
pub request_headers: Option<RequestHeadersMsg>,
pub total_time: Duration,
pub security_state: String,
pub event_timing: Option<Timings>,
@ -176,7 +162,7 @@ struct GetResponseCookiesReply {
cookies: Vec<u8>,
}
#[derive(Serialize)]
#[derive(Clone, Default, Serialize)]
pub struct Timings {
blocked: u32,
dns: u32,
@ -224,15 +210,19 @@ impl Actor for NetworkEventActor {
let mut headers = Vec::new();
let mut raw_headers_string = "".to_owned();
let mut headers_size = 0;
for (name, value) in self.request.headers.iter() {
let value = &value.to_str().unwrap().to_string();
raw_headers_string = raw_headers_string + name.as_str() + ":" + value + "\r\n";
headers_size += name.as_str().len() + value.len();
headers.push(Header {
name: name.as_str().to_owned(),
value: value.to_owned(),
});
if let Some(ref headers_map) = self.request_headers_raw {
for (name, value) in headers_map.iter() {
let value = &value.to_str().unwrap().to_string();
raw_headers_string =
raw_headers_string + name.as_str() + ":" + value + "\r\n";
headers_size += name.as_str().len() + value.len();
headers.push(Header {
name: name.as_str().to_owned(),
value: value.to_owned(),
});
}
}
let msg = GetRequestHeadersReply {
from: self.name(),
headers,
@ -244,13 +234,13 @@ impl Actor for NetworkEventActor {
},
"getRequestCookies" => {
let mut cookies = Vec::new();
for cookie in self.request.headers.get_all(header::COOKIE) {
if let Ok(cookie_value) = String::from_utf8(cookie.as_bytes().to_vec()) {
cookies = cookie_value.into_bytes();
if let Some(ref headers) = self.request_headers_raw {
for cookie in headers.get_all(header::COOKIE) {
if let Ok(cookie_value) = String::from_utf8(cookie.as_bytes().to_vec()) {
cookies = cookie_value.into_bytes();
}
}
}
let msg = GetRequestCookiesReply {
from: self.name(),
cookies,
@ -261,14 +251,14 @@ impl Actor for NetworkEventActor {
"getRequestPostData" => {
let msg = GetRequestPostDataReply {
from: self.name(),
post_data: self.request.body.clone(),
post_data_discarded: false,
post_data: self.request_body.clone(),
post_data_discarded: self.request_body.is_none(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"getResponseHeaders" => {
if let Some(ref response_headers) = self.response.headers {
if let Some(ref response_headers) = self.response_headers_raw {
let mut headers = vec![];
let mut raw_headers_string = "".to_owned();
let mut headers_size = 0;
@ -296,12 +286,13 @@ impl Actor for NetworkEventActor {
"getResponseCookies" => {
let mut cookies = Vec::new();
// TODO: This seems quite broken
for cookie in self.request.headers.get_all(header::SET_COOKIE) {
if let Ok(cookie_value) = String::from_utf8(cookie.as_bytes().to_vec()) {
cookies = cookie_value.into_bytes();
if let Some(ref headers) = self.response_headers_raw {
for cookie in headers.get_all(header::SET_COOKIE) {
if let Ok(cookie_value) = String::from_utf8(cookie.as_bytes().to_vec()) {
cookies = cookie_value.into_bytes();
}
}
}
let msg = GetResponseCookiesReply {
from: self.name(),
cookies,
@ -312,22 +303,16 @@ impl Actor for NetworkEventActor {
"getResponseContent" => {
let msg = GetResponseContentReply {
from: self.name(),
content: self.response.body.clone(),
content_discarded: self.response.body.is_none(),
content: self.response_body.clone(),
content_discarded: self.response_body.is_none(),
};
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
"getEventTimings" => {
// TODO: This is a fake timings msg
let timings_obj = Timings {
blocked: 0,
dns: 0,
connect: self.request.connect_time.as_millis() as u64,
send: self.request.send_time.as_millis() as u64,
wait: 0,
receive: 0,
};
let timings_obj = self.event_timing.clone().unwrap_or_default();
// Might use the one on self
let total = timings_obj.connect + timings_obj.send;
// TODO: Send the correct values for all these fields.
let msg = GetEventTimingsReply {
@ -356,67 +341,60 @@ impl Actor for NetworkEventActor {
impl NetworkEventActor {
pub fn new(name: String) -> NetworkEventActor {
let request = HttpRequest {
url: String::new(),
method: Method::GET,
headers: HeaderMap::new(),
body: None,
started_date_time: SystemTime::now(),
time_stamp: SystemTime::now()
NetworkEventActor {
name,
is_xhr: false,
request_url: String::new(),
request_method: Method::GET,
request_started: SystemTime::now(),
request_time_stamp: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64,
send_time: Duration::ZERO,
connect_time: Duration::ZERO,
};
let response = HttpResponse {
headers: None,
status: HttpStatus::default(),
body: None,
};
NetworkEventActor {
name,
request: request.clone(),
response: response.clone(),
is_xhr: false,
request_headers_raw: None,
request_body: None,
request_cookies: None,
request_headers: None,
response_headers_raw: None,
response_body: None,
response_content: None,
response_start: None,
response_cookies: None,
response_headers: None,
request_cookies: None,
request_headers: None,
total_time: Self::total_time(&request),
security_state: "insecure".to_owned(), // Default security state
total_time: Duration::ZERO,
security_state: "insecure".to_owned(),
event_timing: None,
}
}
pub fn add_request(&mut self, request: DevtoolsHttpRequest) {
request.url.as_str().clone_into(&mut self.request.url);
self.request.method = request.method.clone();
self.request.headers = request.headers.clone();
self.request.body = request.body;
self.request.started_date_time = request.started_date_time;
self.request.time_stamp = request.time_stamp;
self.request.connect_time = request.connect_time;
self.request.send_time = request.send_time;
self.is_xhr = request.is_xhr;
self.request_cookies = Some(Self::request_cookies(&request));
self.request_headers = Some(Self::request_headers(&request));
self.total_time = Self::total_time(&request);
self.event_timing = Some(Self::event_timing(&request));
self.request_url = request.url.to_string();
self.request_method = request.method;
self.request_started = request.started_date_time;
self.request_time_stamp = request.time_stamp;
self.request_body = request.body.clone();
self.request_headers_raw = Some(request.headers.clone());
}
pub fn add_response(&mut self, response: DevtoolsHttpResponse) {
self.response.headers.clone_from(&response.headers);
self.response.status = response.status;
self.response.body = response.body;
self.response_headers = Some(Self::response_headers(&response));
self.response_cookies = Some(Self::response_cookies(&response));
self.response_start = Some(Self::response_start(&response));
self.response_content = Some(Self::response_content(&response));
self.response_body = response.body.clone();
self.response_headers_raw = response.headers.clone();
}
pub fn event_actor(&self) -> EventActor {
// TODO: Send the correct values for startedDateTime, isXHR, private
let started_datetime_rfc3339 = match Local.timestamp_millis_opt(
self.request
.started_date_time
self.request_started
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64,
@ -426,7 +404,7 @@ impl NetworkEventActor {
LocalResult::Ambiguous(date_time, _) => date_time.to_rfc3339().to_string(),
};
let cause_type = match self.request.url.as_str() {
let cause_type = match self.request_url.as_str() {
// Adjust based on request data
url if url.ends_with(".css") => "stylesheet",
url if url.ends_with(".js") => "script",
@ -436,10 +414,10 @@ impl NetworkEventActor {
EventActor {
actor: self.name(),
url: self.request.url.clone(),
method: format!("{}", self.request.method),
url: self.request_url.clone(),
method: format!("{}", self.request_method),
started_date_time: started_datetime_rfc3339,
time_stamp: self.request.time_stamp,
time_stamp: self.request_time_stamp,
is_xhr: self.is_xhr,
private: false,
cause: Cause {
@ -449,8 +427,7 @@ impl NetworkEventActor {
}
}
#[allow(dead_code)]
pub fn response_start(response: &HttpResponse) -> ResponseStartMsg {
pub fn response_start(response: &DevtoolsHttpResponse) -> ResponseStartMsg {
// TODO: Send the correct values for all these fields.
let h_size = response.headers.as_ref().map(|h| h.len()).unwrap_or(0);
let status = &response.status;
@ -467,16 +444,14 @@ impl NetworkEventActor {
}
}
#[allow(dead_code)]
pub fn response_content(response: &HttpResponse) -> ResponseContentMsg {
let mime_type = if let Some(ref headers) = response.headers {
match headers.typed_get::<ContentType>() {
Some(ct) => ct.to_string(),
None => "".to_string(),
}
} else {
"".to_string()
};
pub fn response_content(response: &DevtoolsHttpResponse) -> ResponseContentMsg {
let mime_type = response
.headers
.as_ref()
.and_then(|h| h.typed_get::<ContentType>())
.map(|ct| ct.to_string())
.unwrap_or_default();
// TODO: Set correct values when response's body is sent to the devtools in http_loader.
ResponseContentMsg {
mime_type,
@ -486,38 +461,33 @@ impl NetworkEventActor {
}
}
#[allow(dead_code)]
pub fn response_cookies(response: &HttpResponse) -> ResponseCookiesMsg {
let mut cookies_size = 0;
if let Some(ref headers) = response.headers {
cookies_size = match headers.typed_get::<Cookie>() {
Some(ref cookie) => cookie.len(),
_ => 0,
};
}
pub fn response_cookies(response: &DevtoolsHttpResponse) -> ResponseCookiesMsg {
let cookies_size = response
.headers
.as_ref()
.map(|headers| headers.get_all("set-cookie").iter().count())
.unwrap_or(0);
ResponseCookiesMsg {
cookies: cookies_size,
}
}
#[allow(dead_code)]
pub fn response_headers(response: &HttpResponse) -> ResponseHeadersMsg {
let mut headers_size = 0;
pub fn response_headers(response: &DevtoolsHttpResponse) -> ResponseHeadersMsg {
let mut header_size = 0;
let mut headers_byte_count = 0;
if let Some(ref headers) = response.headers {
headers_size = headers.len();
for (name, value) in headers.iter() {
header_size += 1;
headers_byte_count += name.as_str().len() + value.len();
}
}
ResponseHeadersMsg {
headers: headers_size,
headers: header_size,
headers_size: headers_byte_count,
}
}
#[allow(dead_code)]
pub fn request_headers(request: &HttpRequest) -> RequestHeadersMsg {
pub fn request_headers(request: &DevtoolsHttpRequest) -> RequestHeadersMsg {
let size = request.headers.iter().fold(0, |acc, (name, value)| {
acc + name.as_str().len() + value.len()
});
@ -527,21 +497,32 @@ impl NetworkEventActor {
}
}
#[allow(dead_code)]
pub fn request_cookies(request: &HttpRequest) -> RequestCookiesMsg {
let cookies_size = match request.headers.typed_get::<Cookie>() {
Some(ref cookie) => cookie.len(),
_ => 0,
};
pub fn request_cookies(request: &DevtoolsHttpRequest) -> RequestCookiesMsg {
let cookies_size = request
.headers
.typed_get::<Cookie>()
.map(|c| c.len())
.unwrap_or(0);
RequestCookiesMsg {
cookies: cookies_size,
}
}
pub fn total_time(request: &HttpRequest) -> Duration {
pub fn total_time(request: &DevtoolsHttpRequest) -> Duration {
request.connect_time + request.send_time
}
pub fn event_timing(request: &DevtoolsHttpRequest) -> Timings {
Timings {
blocked: 0,
dns: 0,
connect: request.connect_time.as_millis() as u64,
send: request.send_time.as_millis() as u64,
wait: 0,
receive: 0,
}
}
fn insert_serialized_map<T: Serialize>(map: &mut Map<String, Value>, obj: &Option<T>) {
if let Some(value) = obj {
if let Ok(Value::Object(serialized)) = serde_json::to_value(value) {

View file

@ -31,22 +31,30 @@ pub(crate) fn handle_network_event(
let mut actors = actors.lock().unwrap();
match network_event {
NetworkEvent::HttpRequest(httprequest) => {
// Scope mutable borrow
let event_actor = {
let (event_actor, resource_updates) = {
let actor = actors.find_mut::<NetworkEventActor>(&netevent_actor_name);
actor.add_request(httprequest);
actor.event_actor()
(actor.event_actor(), actor.resource_updates())
};
let browsing_context_actor =
actors.find::<BrowsingContextActor>(&browsing_context_actor_name);
for stream in &mut connections {
// Notify that a new network event has started
browsing_context_actor.resource_array(
event_actor.clone(),
"network-event".to_string(),
ResourceArrayType::Available,
stream,
);
// Also push initial resource update (request headers, cookies)
browsing_context_actor.resource_array(
resource_updates.clone(),
"network-event".to_string(),
ResourceArrayType::Updated,
stream,
);
}
},
NetworkEvent::HttpResponse(httpresponse) => {