@@ -135,110 +135,120 @@ export class SendStore {
135135 const requestInput = sendRequest.request;
136136 const pendingRequestDeferred = getObservableDeferred();
137137 const abortController = new AbortController();
138- runInAction(() => {
139- sendRequest.sentExchange = undefined;
140138
141- sendRequest.pendingSend = {
142- promise: pendingRequestDeferred.promise,
143- abort: () => abortController.abort()
144- };
145-
146- const clearPending = action(() => { sendRequest.pendingSend = undefined; });
147- sendRequest.pendingSend.promise.then(clearPending, clearPending);
148- });
139+ try {
140+ runInAction(() => {
141+ sendRequest.sentExchange = undefined;
149142
150- const exchangeId = uuid();
143+ sendRequest.pendingSend = {
144+ promise: pendingRequestDeferred.promise,
145+ abort: () => abortController.abort()
146+ };
151147
152- const passthroughOptions = this.rulesStore.activePassthroughOptions;
153-
154- const url = new URL(requestInput.url);
155- const effectivePort = getEffectivePort(url);
156- const hostWithPort = `${url.hostname}:${effectivePort}`;
157- const clientCertificate = passthroughOptions.clientCertificateHostMap?.[hostWithPort] ||
158- passthroughOptions.clientCertificateHostMap?.[url.hostname!] ||
159- undefined;
160-
161- const requestOptions = {
162- ignoreHostHttpsErrors: passthroughOptions.ignoreHostHttpsErrors,
163- trustAdditionalCAs: this.rulesStore.additionalCaCertificates.map((cert) =>
164- ({ cert: cert.rawPEM })
165- ),
166- clientCertificate,
167- proxyConfig: getProxyConfig(this.rulesStore.proxyConfig),
168- lookupOptions: passthroughOptions.lookupOptions
169- };
148+ const clearPending = action(() => { sendRequest.pendingSend = undefined; });
149+ sendRequest.pendingSend.promise.then(clearPending, clearPending);
150+ });
170151
171- const encodedBody = await requestInput.rawBody.encodingBestEffortPromise;
152+ const exchangeId = uuid();
153+
154+ const passthroughOptions = this.rulesStore.activePassthroughOptions;
155+
156+ const url = new URL(requestInput.url);
157+ const effectivePort = getEffectivePort(url);
158+ const hostWithPort = `${url.hostname}:${effectivePort}`;
159+ const clientCertificate = passthroughOptions.clientCertificateHostMap?.[hostWithPort] ||
160+ passthroughOptions.clientCertificateHostMap?.[url.hostname!] ||
161+ undefined;
162+
163+ const requestOptions = {
164+ ignoreHostHttpsErrors: passthroughOptions.ignoreHostHttpsErrors,
165+ trustAdditionalCAs: this.rulesStore.additionalCaCertificates.map((cert) =>
166+ ({ cert: cert.rawPEM })
167+ ),
168+ clientCertificate,
169+ proxyConfig: getProxyConfig(this.rulesStore.proxyConfig),
170+ lookupOptions: passthroughOptions.lookupOptions
171+ };
172172
173- const responseStream = await ServerApi.sendRequest(
174- {
175- url: requestInput.url,
173+ const encodedBody = await requestInput.rawBody.encodingBestEffortPromise;
174+
175+ const responseStream = await ServerApi.sendRequest(
176+ {
177+ url: requestInput.url,
178+ method: requestInput.method,
179+ headers: requestInput.headers,
180+ rawBody: encodedBody
181+ },
182+ requestOptions,
183+ abortController.signal
184+ );
185+
186+ const exchange = this.eventStore.recordSentRequest({
187+ id: exchangeId,
188+ httpVersion: '1.1',
189+ matchedRuleId: false,
176190 method: requestInput.method,
177- headers: requestInput.headers,
178- rawBody: encodedBody
179- },
180- requestOptions,
181- abortController.signal
182- );
183-
184- const exchange = this.eventStore.recordSentRequest({
185- id: exchangeId,
186- httpVersion: '1.1',
187- matchedRuleId: false,
188- method: requestInput.method,
189- url: requestInput.url,
190- protocol: url.protocol.slice(0, -1),
191- path: url.pathname,
192- hostname: url.hostname,
193- headers: rawHeadersToHeaders(requestInput.headers),
194- rawHeaders: _.cloneDeep(requestInput.headers),
195- body: { buffer: encodedBody },
196- timingEvents: {
197- startTime: Date.now()
198- } as TimingEvents,
199- tags: ['httptoolkit:manually-sent-request']
200- });
191+ url: requestInput.url,
192+ protocol: url.protocol.slice(0, -1),
193+ path: url.pathname,
194+ hostname: url.hostname,
195+ headers: rawHeadersToHeaders(requestInput.headers),
196+ rawHeaders: _.cloneDeep(requestInput.headers),
197+ body: { buffer: encodedBody },
198+ timingEvents: {
199+ startTime: Date.now()
200+ } as TimingEvents,
201+ tags: ['httptoolkit:manually-sent-request']
202+ });
201203
202- // Keep the exchange up to date as response data arrives:
203- trackResponseEvents(responseStream, exchange)
204- .catch(action((error: ErrorLike & { timingEvents?: TimingEvents }) => {
205- if (error.name === 'AbortError' && abortController.signal.aborted) {
206- const startTime = exchange.timingEvents.startTime!; // Always set in Send case (just above)
207- // Make a guess at an aborted timestamp, since this error won't give us one automatically:
208- const durationBeforeAbort = Date.now() - startTime;
209- const startTimestamp = exchange.timingEvents.startTimestamp ?? startTime;
210- const abortedTimestamp = startTimestamp + durationBeforeAbort;
211-
212- exchange.markAborted({
213- id: exchange.id,
214- error: {
215- message: 'Request cancelled'
216- },
217- timingEvents: {
218- startTimestamp,
219- abortedTimestamp,
220- ...exchange.timingEvents,
221- ...error.timingEvents
222- } as TimingEvents,
223- tags: ['client-error:ECONNABORTED']
224- });
225- } else {
226- exchange.markAborted({
227- id: exchange.id,
228- error: error,
229- timingEvents: {
230- ...exchange.timingEvents as TimingEvents,
231- ...error.timingEvents
232- },
233- tags: error.code ? [`passthrough-error:${error.code}`] : []
234- });
235- }
236- }))
237- .then(() => pendingRequestDeferred.resolve());
204+ // Keep the exchange up to date as response data arrives:
205+ trackResponseEvents(responseStream, exchange)
206+ .catch(action((error: ErrorLike & { timingEvents?: TimingEvents }) => {
207+ if (error.name === 'AbortError' && abortController.signal.aborted) {
208+ const startTime = exchange.timingEvents.startTime!; // Always set in Send case (just above)
209+ // Make a guess at an aborted timestamp, since this error won't give us one automatically:
210+ const durationBeforeAbort = Date.now() - startTime;
211+ const startTimestamp = exchange.timingEvents.startTimestamp ?? startTime;
212+ const abortedTimestamp = startTimestamp + durationBeforeAbort;
213+
214+ exchange.markAborted({
215+ id: exchange.id,
216+ error: {
217+ message: 'Request cancelled'
218+ },
219+ timingEvents: {
220+ startTimestamp,
221+ abortedTimestamp,
222+ ...exchange.timingEvents,
223+ ...error.timingEvents
224+ } as TimingEvents,
225+ tags: ['client-error:ECONNABORTED']
226+ });
227+ } else {
228+ exchange.markAborted({
229+ id: exchange.id,
230+ error: error,
231+ timingEvents: {
232+ ...exchange.timingEvents as TimingEvents,
233+ ...error.timingEvents
234+ },
235+ tags: error.code ? [`passthrough-error:${error.code}`] : []
236+ });
237+ }
238+ }))
239+ .then(() => pendingRequestDeferred.resolve());
238240
239- runInAction(() => {
240- sendRequest.sentExchange = exchange;
241- });
241+ runInAction(() => {
242+ sendRequest.sentExchange = exchange;
243+ });
244+ } catch (e: any) {
245+ pendingRequestDeferred.reject(e);
246+ runInAction(() => {
247+ sendRequest.pendingSend = undefined;
248+ sendRequest.sentExchange = undefined;
249+ });
250+ throw e;
251+ }
242252 }
243253
244254}
0 commit comments