Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/mitm/handlers/BaseHttpHandler.ts
1030 views
1
import Log from '@secret-agent/commons/Logger';
2
import * as http from 'http';
3
import * as http2 from 'http2';
4
import { ClientHttp2Stream } from 'http2';
5
import IMitmRequestContext from '../interfaces/IMitmRequestContext';
6
import BlockHandler from './BlockHandler';
7
import HeadersHandler from './HeadersHandler';
8
import MitmRequestContext from '../lib/MitmRequestContext';
9
import HttpResponseCache from '../lib/HttpResponseCache';
10
import ResourceState from '../interfaces/ResourceState';
11
12
const { log } = Log(module);
13
14
export default abstract class BaseHttpHandler {
15
public readonly context: IMitmRequestContext;
16
17
protected abstract onError(kind: string, error: Error);
18
19
protected constructor(
20
request: Pick<
21
IMitmRequestContext,
22
'requestSession' | 'isSSL' | 'clientToProxyRequest' | 'proxyToClientResponse'
23
>,
24
isUpgrade: boolean,
25
responseCache: HttpResponseCache,
26
) {
27
this.context = MitmRequestContext.create({ ...request, isUpgrade }, responseCache);
28
}
29
30
protected async createProxyToServerRequest(): Promise<
31
http.ClientRequest | http2.ClientHttp2Stream
32
> {
33
const context = this.context;
34
const session = context.requestSession;
35
36
try {
37
// track request
38
session.trackResourceRedirects(this.context);
39
session.emit('request', MitmRequestContext.toEmittedResource(this.context));
40
41
if (session.isClosing) return context.setState(ResourceState.SessionClosed);
42
43
// need to determine resource type before blocking
44
await HeadersHandler.determineResourceType(context);
45
46
if (BlockHandler.shouldBlockRequest(context)) {
47
context.setState(ResourceState.Blocked);
48
log.info(`Http.RequestBlocked`, {
49
sessionId: session.sessionId,
50
url: context.url.href,
51
});
52
await context.browserHasRequested;
53
session.emit('response', MitmRequestContext.toEmittedResource(this.context));
54
// already wrote reply
55
return;
56
}
57
58
// do one more check on the session before doing a connect
59
if (session.isClosing) return context.setState(ResourceState.SessionClosed);
60
61
const request = await session.requestAgent.request(context);
62
this.context.proxyToServerRequest = request;
63
this.context.eventSubscriber.on(
64
request,
65
'error',
66
this.onError.bind(this, 'ProxyToServer.RequestError'),
67
);
68
69
if (this.context.isServerHttp2) {
70
const h2Request = request as ClientHttp2Stream;
71
this.bindHttp2ErrorListeners('ProxyToH2Server', h2Request, h2Request.session);
72
}
73
74
return this.context.proxyToServerRequest;
75
} catch (err) {
76
this.onError('ProxyToServer.RequestHandlerError', err);
77
}
78
}
79
80
protected cleanup(): void {
81
this.context.eventSubscriber.close('error');
82
this.context.proxyToServerRequest = null;
83
this.context.clientToProxyRequest = null;
84
this.context.requestSession = null;
85
this.context.proxyToClientResponse = null;
86
this.context.proxyToServerMitmSocket = null;
87
this.context.cacheHandler = null;
88
this.context.browserHasRequested = null;
89
}
90
91
protected bindHttp2ErrorListeners(
92
source: string,
93
stream: http2.Http2Stream,
94
session: http2.Http2Session,
95
): void {
96
if (!stream.listenerCount('error')) {
97
this.context.eventSubscriber.on(
98
stream,
99
'error',
100
this.onError.bind(this, `${source}.Http2StreamError`),
101
);
102
}
103
104
this.context.eventSubscriber.on(stream, 'streamClosed', code => {
105
if (!code) return;
106
this.onError(`${source}.Http2StreamError`, new Error(`Stream Closed ${code}`));
107
});
108
109
if (session && !session.listenerCount('error')) {
110
this.context.eventSubscriber.on(
111
session,
112
'error',
113
this.onError.bind(this, `${source}.Http2SessionError`),
114
);
115
}
116
}
117
}
118
119