Show full URL parts in Timeline debug view (#373)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gregory Schier
2026-01-28 08:41:17 -08:00
committed by GitHub
parent 1b0315165f
commit f056894ddb
4 changed files with 73 additions and 12 deletions

View File

@@ -31,7 +31,14 @@ pub enum HttpResponseEvent {
}, },
SendUrl { SendUrl {
method: String, method: String,
scheme: String,
username: String,
password: String,
host: String,
port: u16,
path: String, path: String,
query: String,
fragment: String,
}, },
ReceiveUrl { ReceiveUrl {
version: Version, version: Version,
@@ -65,7 +72,16 @@ impl Display for HttpResponseEvent {
}; };
write!(f, "* Redirect {} -> {} ({})", status, url, behavior_str) write!(f, "* Redirect {} -> {} ({})", status, url, behavior_str)
} }
HttpResponseEvent::SendUrl { method, path } => write!(f, "> {} {}", method, path), HttpResponseEvent::SendUrl { method, scheme, username, password, host, port, path, query, fragment } => {
let auth_str = if username.is_empty() && password.is_empty() {
String::new()
} else {
format!("{}:{}@", username, password)
};
let query_str = if query.is_empty() { String::new() } else { format!("?{}", query) };
let fragment_str = if fragment.is_empty() { String::new() } else { format!("#{}", fragment) };
write!(f, "> {} {}://{}{}:{}{}{}{}", method, scheme, auth_str, host, port, path, query_str, fragment_str)
}
HttpResponseEvent::ReceiveUrl { version, status } => { HttpResponseEvent::ReceiveUrl { version, status } => {
write!(f, "< {} {}", version_to_str(version), status) write!(f, "< {} {}", version_to_str(version), status)
} }
@@ -104,7 +120,9 @@ impl From<HttpResponseEvent> for yaak_models::models::HttpResponseEventData {
RedirectBehavior::DropBody => "drop_body".to_string(), RedirectBehavior::DropBody => "drop_body".to_string(),
}, },
}, },
HttpResponseEvent::SendUrl { method, path } => D::SendUrl { method, path }, HttpResponseEvent::SendUrl { method, scheme, username, password, host, port, path, query, fragment } => {
D::SendUrl { method, scheme, username, password, host, port, path, query, fragment }
}
HttpResponseEvent::ReceiveUrl { version, status } => { HttpResponseEvent::ReceiveUrl { version, status } => {
D::ReceiveUrl { version: format!("{:?}", version), status } D::ReceiveUrl { version: format!("{:?}", version), status }
} }
@@ -415,8 +433,15 @@ impl HttpSender for ReqwestSender {
)); ));
send_event(HttpResponseEvent::SendUrl { send_event(HttpResponseEvent::SendUrl {
path: sendable_req.url().path().to_string(),
method: sendable_req.method().to_string(), method: sendable_req.method().to_string(),
scheme: sendable_req.url().scheme().to_string(),
username: sendable_req.url().username().to_string(),
password: sendable_req.url().password().unwrap_or_default().to_string(),
host: sendable_req.url().host_str().unwrap_or_default().to_string(),
port: sendable_req.url().port_or_known_default().unwrap_or(0),
path: sendable_req.url().path().to_string(),
query: sendable_req.url().query().unwrap_or_default().to_string(),
fragment: sendable_req.url().fragment().unwrap_or_default().to_string(),
}); });
let mut request_headers = Vec::new(); let mut request_headers = Vec::new();

View File

@@ -49,7 +49,7 @@ export type HttpResponseEvent = { model: "http_response_event", id: string, crea
* This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support. * This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support.
* The `From` impl is in yaak-http to avoid circular dependencies. * The `From` impl is in yaak-http to avoid circular dependencies.
*/ */
export type HttpResponseEventData = { "type": "setting", name: string, value: string, } | { "type": "info", message: string, } | { "type": "redirect", url: string, status: number, behavior: string, } | { "type": "send_url", method: string, path: string, } | { "type": "receive_url", version: string, status: string, } | { "type": "header_up", name: string, value: string, } | { "type": "header_down", name: string, value: string, } | { "type": "chunk_sent", bytes: number, } | { "type": "chunk_received", bytes: number, } | { "type": "dns_resolved", hostname: string, addresses: Array<string>, duration: bigint, overridden: boolean, }; export type HttpResponseEventData = { "type": "setting", name: string, value: string, } | { "type": "info", message: string, } | { "type": "redirect", url: string, status: number, behavior: string, } | { "type": "send_url", method: string, scheme: string, username: string, password: string, host: string, port: number, path: string, query: string, fragment: string, } | { "type": "receive_url", version: string, status: string, } | { "type": "header_up", name: string, value: string, } | { "type": "header_down", name: string, value: string, } | { "type": "chunk_sent", bytes: number, } | { "type": "chunk_received", bytes: number, } | { "type": "dns_resolved", hostname: string, addresses: Array<string>, duration: bigint, overridden: boolean, };
export type HttpResponseHeader = { name: string, value: string, }; export type HttpResponseHeader = { name: string, value: string, };

View File

@@ -1495,7 +1495,21 @@ pub enum HttpResponseEventData {
}, },
SendUrl { SendUrl {
method: String, method: String,
#[serde(default)]
scheme: String,
#[serde(default)]
username: String,
#[serde(default)]
password: String,
#[serde(default)]
host: String,
#[serde(default)]
port: u16,
path: String, path: String,
#[serde(default)]
query: String,
#[serde(default)]
fragment: String,
}, },
ReceiveUrl { ReceiveUrl {
version: String, version: String,

View File

@@ -149,14 +149,27 @@ function EventDetails({
); );
} }
// Request URL - show method and path separately // Request URL - show all URL parts separately
if (e.type === 'send_url') { if (e.type === 'send_url') {
const auth = e.username || e.password ? `${e.username}:${e.password}@` : '';
const isDefaultPort =
(e.scheme === 'http' && e.port === 80) || (e.scheme === 'https' && e.port === 443);
const portStr = isDefaultPort ? '' : `:${e.port}`;
const query = e.query ? `?${e.query}` : '';
const fragment = e.fragment ? `#${e.fragment}` : '';
const fullUrl = `${e.scheme}://${auth}${e.host}${portStr}${e.path}${query}${fragment}`;
return ( return (
<KeyValueRows> <KeyValueRows>
<KeyValueRow label="Method"> <KeyValueRow label="URL">{fullUrl}</KeyValueRow>
<HttpMethodTagRaw forceColor method={e.method} /> <KeyValueRow label="Method">{e.method}</KeyValueRow>
</KeyValueRow> <KeyValueRow label="Scheme">{e.scheme}</KeyValueRow>
{e.username ? <KeyValueRow label="Username">{e.username}</KeyValueRow> : null}
{e.password ? <KeyValueRow label="Password">{e.password}</KeyValueRow> : null}
<KeyValueRow label="Host">{e.host}</KeyValueRow>
{!isDefaultPort ? <KeyValueRow label="Port">{e.port}</KeyValueRow> : null}
<KeyValueRow label="Path">{e.path}</KeyValueRow> <KeyValueRow label="Path">{e.path}</KeyValueRow>
{e.query ? <KeyValueRow label="Query">{e.query}</KeyValueRow> : null}
{e.fragment ? <KeyValueRow label="Fragment">{e.fragment}</KeyValueRow> : null}
</KeyValueRows> </KeyValueRows>
); );
} }
@@ -244,7 +257,10 @@ type EventTextParts = { prefix: '>' | '<' | '*'; text: string };
function getEventTextParts(event: HttpResponseEventData): EventTextParts { function getEventTextParts(event: HttpResponseEventData): EventTextParts {
switch (event.type) { switch (event.type) {
case 'send_url': case 'send_url':
return { prefix: '>', text: `${event.method} ${event.path}` }; return {
prefix: '>',
text: `${event.method} ${event.path}${event.query ? `?${event.query}` : ''}${event.fragment ? `#${event.fragment}` : ''}`,
};
case 'receive_url': case 'receive_url':
return { prefix: '<', text: `${event.version} ${event.status}` }; return { prefix: '<', text: `${event.version} ${event.status}` };
case 'header_up': case 'header_up':
@@ -265,9 +281,15 @@ function getEventTextParts(event: HttpResponseEventData): EventTextParts {
return { prefix: '*', text: `[${formatBytes(event.bytes)} received]` }; return { prefix: '*', text: `[${formatBytes(event.bytes)} received]` };
case 'dns_resolved': case 'dns_resolved':
if (event.overridden) { if (event.overridden) {
return { prefix: '*', text: `DNS override ${event.hostname} -> ${event.addresses.join(', ')}` }; return {
prefix: '*',
text: `DNS override ${event.hostname} -> ${event.addresses.join(', ')}`,
};
} }
return { prefix: '*', text: `DNS resolved ${event.hostname} to ${event.addresses.join(', ')} (${event.duration}ms)` }; return {
prefix: '*',
text: `DNS resolved ${event.hostname} to ${event.addresses.join(', ')} (${event.duration}ms)`,
};
default: default:
return { prefix: '*', text: '[unknown event]' }; return { prefix: '*', text: '[unknown event]' };
} }
@@ -314,7 +336,7 @@ function getEventDisplay(event: HttpResponseEventData): EventDisplay {
icon: 'arrow_big_up_dash', icon: 'arrow_big_up_dash',
color: 'primary', color: 'primary',
label: 'Request', label: 'Request',
summary: `${event.method} ${event.path}`, summary: `${event.method} ${event.path}${event.query ? `?${event.query}` : ''}${event.fragment ? `#${event.fragment}` : ''}`,
}; };
case 'receive_url': case 'receive_url':
return { return {