Skip to content

Commit 225c219

Browse files
committed
v0:feat: implement timeout and enhanced logging for fetchPage method
1 parent 75a1db4 commit 225c219

1 file changed

Lines changed: 100 additions & 32 deletions

File tree

apps/workers/v0/src/services/scrape/scrape.service.ts

Lines changed: 100 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -65,46 +65,114 @@ export class ScrapeService {
6565
url: string,
6666
options: { isIframeAllowed?: boolean } = { isIframeAllowed: true },
6767
): Promise<string | FetchPageResult> {
68-
const response = await fetch(url);
68+
// Add timeout configuration for production reliability
69+
const timeoutMs = 10000; // 10 seconds timeout
70+
const abortController = new AbortController();
71+
const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
6972

70-
// Check if the response is successful
71-
if (!response.ok) {
72-
throw new Error(`HTTP status ${response.status}`);
73-
}
73+
console.log('[PERF] Scraping fetchPage started:', {
74+
url,
75+
timeoutMs,
76+
timestamp: new Date().toISOString(),
77+
});
7478

75-
// Check content type to ensure it's HTML or text
76-
const contentType = response.headers.get('content-type') || '';
79+
const fetchStart = Date.now();
7780

78-
// More lenient content type check - if it contains text or html in any form
79-
if (
80-
!contentType.toLowerCase().includes('html') &&
81-
!contentType.toLowerCase().includes('text') &&
82-
!contentType.toLowerCase().includes('xml')
83-
) {
84-
throw new Error(
85-
`URL content type "${contentType}" is not allowed for scraping`,
86-
);
87-
}
81+
try {
82+
const response = await fetch(url, {
83+
signal: abortController.signal,
84+
// Add additional fetch options for better reliability
85+
headers: {
86+
'User-Agent': 'Deepcrawl-Bot/1.0 (+https://deepcrawl.dev)',
87+
Accept:
88+
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
89+
'Accept-Language': 'en-US,en;q=0.5',
90+
'Accept-Encoding': 'gzip, deflate',
91+
DNT: '1',
92+
Connection: 'keep-alive',
93+
'Upgrade-Insecure-Requests': '1',
94+
},
95+
});
8896

89-
const html = await response.text();
97+
clearTimeout(timeoutId);
98+
const fetchTime = Date.now() - fetchStart;
9099

91-
if (!options.isIframeAllowed) {
92-
return html;
93-
}
100+
console.log('[PERF] Scraping fetchPage response received:', {
101+
url,
102+
fetchTime,
103+
status: response.status,
104+
contentType: response.headers.get('content-type'),
105+
timestamp: new Date().toISOString(),
106+
});
107+
108+
// Check if the response is successful
109+
if (!response.ok) {
110+
throw new Error(`HTTP status ${response.status}`);
111+
}
112+
113+
// Check content type to ensure it's HTML or text
114+
const contentType = response.headers.get('content-type') || '';
115+
116+
// More lenient content type check - if it contains text or html in any form
117+
if (
118+
!contentType.toLowerCase().includes('html') &&
119+
!contentType.toLowerCase().includes('text') &&
120+
!contentType.toLowerCase().includes('xml')
121+
) {
122+
throw new Error(
123+
`URL content type "${contentType}" is not allowed for scraping`,
124+
);
125+
}
126+
127+
const textStart = Date.now();
128+
const html = await response.text();
129+
const textTime = Date.now() - textStart;
130+
131+
console.log('[PERF] Scraping fetchPage text extracted:', {
132+
url,
133+
textTime,
134+
htmlSize: html.length,
135+
totalTime: Date.now() - fetchStart,
136+
timestamp: new Date().toISOString(),
137+
});
94138

95-
// Extract relevant headers
96-
const xFrameOptions = response.headers.get('x-frame-options');
97-
const contentSecurityPolicy = response.headers.get(
98-
'content-security-policy',
99-
);
139+
if (!options.isIframeAllowed) {
140+
return html;
141+
}
100142

101-
// Determine if iframe embedding is allowed
102-
const isIframeAllowed = this.isIframeAllowed(
103-
xFrameOptions,
104-
contentSecurityPolicy,
105-
);
143+
// Extract relevant headers
144+
const xFrameOptions = response.headers.get('x-frame-options');
145+
const contentSecurityPolicy = response.headers.get(
146+
'content-security-policy',
147+
);
106148

107-
return { html, isIframeAllowed };
149+
// Determine if iframe embedding is allowed
150+
const isIframeAllowed = this.isIframeAllowed(
151+
xFrameOptions,
152+
contentSecurityPolicy,
153+
);
154+
155+
return { html, isIframeAllowed };
156+
} catch (error) {
157+
clearTimeout(timeoutId); // Ensure timeout is cleared on error
158+
159+
const errorTime = Date.now() - fetchStart;
160+
console.error('[PERF] Scraping fetchPage error:', {
161+
url,
162+
errorTime,
163+
error: error instanceof Error ? error.message : String(error),
164+
errorName: error instanceof Error ? error.name : 'Unknown',
165+
isTimeout: error instanceof Error && error.name === 'AbortError',
166+
timestamp: new Date().toISOString(),
167+
});
168+
169+
// Provide more specific error messages
170+
if (error instanceof Error && error.name === 'AbortError') {
171+
throw new Error(`Request timeout after ${timeoutMs}ms for URL: ${url}`);
172+
}
173+
174+
throw error;
175+
}
108176
}
109177

110178
private async fetchMetaFiles(

0 commit comments

Comments
 (0)