src/server.js
/**
     * Authenticates a new request and determines upstream proxy URL using the user function.
     * Returns a promise resolving to an object that can be passed to construcot of one of the HandlerXxx classes.
     * @param request
     */
Authenticates a new request and determines upstream proxy URL using the user function.
Returns a promise resolving to an object that can be passed to construcot of one of the HandlerXxx classes.
@param request
Authenticates a new request and determines upstream proxy URL using the user function . Returns a promise resolving to an object that can be passed to construcot of one of the HandlerXxx classes . @param request
prepareRequestHandling(request) {
        const handlerOpts = {
            server: this,
            id: ++this.lastHandlerId,
            srcRequest: request,
            srcHead: null,
            trgParsed: null,
            upstreamProxyUrlParsed: null,
        };
        this.log(handlerOpts.id, `!!! Handling ${request.method} ${request.url} HTTP/${request.httpVersion}`);
        const { socket } = request;
        let isHttp = false;
        return Promise.resolve()
            .then(() => {
                if (request.method === 'CONNECT') {
                    handlerOpts.trgParsed = parseHostHeader(request.url);
                    if (!handlerOpts.trgParsed) {
                        throw new RequestError(`Target "${request.url}" could not be parsed`, 400);
                    }
                    this.stats.connectRequestCount++;
                } else {
                    let parsed;
                    try {
                        parsed = parseUrl(request.url);
                    } catch (e) {
                        throw new RequestError(`Target "${request.url}" could not be parsed`, 400);
                    }
                    if (!parsed.protocol) {
                        throw new RequestError('Hey, good try, but I\'m a HTTP proxy, not your ordinary web server :)', 400);
                    }
                    if (parsed.protocol !== 'http:') {
                        throw new RequestError(`Only HTTP protocol is supported (was ${parsed.protocol})`, 400);
                    }
                    handlerOpts.trgParsed = parsed;
                    isHttp = true;
                    this.stats.httpRequestCount++;
                }
                handlerOpts.trgParsed.port = handlerOpts.trgParsed.port || DEFAULT_TARGET_PORT;
                if (!this.prepareRequestFunction) return { requestAuthentication: false, upstreamProxyUrlParsed: null };
                socket.pause();
                const funcOpts = {
                    connectionId: handlerOpts.id,
                    request,
                    username: null,
                    password: null,
                    hostname: handlerOpts.trgParsed.hostname,
                    port: handlerOpts.trgParsed.port,
                    isHttp,
                };
                const proxyAuth = request.headers['proxy-authorization'];
                if (proxyAuth) {
                    const auth = parseProxyAuthorizationHeader(proxyAuth);
                    if (!auth) {
                        throw new RequestError('Invalid "Proxy-Authorization" header', 400);
                    }
                    if (auth.type !== 'Basic') {
                        throw new RequestError('The "Proxy-Authorization" header must have the "Basic" type.', 400);
                    }
                    funcOpts.username = auth.username;
                    funcOpts.password = auth.password;
                }
                return this.prepareRequestFunction(funcOpts);
            })
            .then((funcResult) => {
                if (funcResult && funcResult.requestAuthentication) {
                    throw new RequestError(funcResult.failMsg || 'Proxy credentials required.', 407);
                }
                if (funcResult && funcResult.upstreamProxyUrl) {
                    try {
                        handlerOpts.upstreamProxyUrlParsed = parseUrl(funcResult.upstreamProxyUrl);
                    } catch (e) {
                        throw new Error(`Invalid "upstreamProxyUrl" provided: ${e} (was "${funcResult.upstreamProxyUrl}"`);
                    }
                    if (!handlerOpts.upstreamProxyUrlParsed.hostname || !handlerOpts.upstreamProxyUrlParsed.port) {
                        throw new Error(`Invalid "upstreamProxyUrl" provided: URL must have hostname and port (was "${funcResult.upstreamProxyUrl}")`); 
                    }
                    if (!this.supportedProxySchemes.has(handlerOpts.upstreamProxyUrlParsed.protocol)) {
                        throw new Error(`Invalid "upstreamProxyUrl" provided: URL must have the "http" or "https" protocol (was "${funcResult.upstreamProxyUrl}")`); 
                    }
                    if (/:/.test(handlerOpts.upstreamProxyUrlParsed.username)) {
                        throw new Error('Invalid "upstreamProxyUrl" provided: The username cannot contain the colon (:) character according to RFC 7617.'); 
                    }
                }
                if (funcResult && funcResult.customResponseFunction) {
                    this.log(handlerOpts.id, 'Using custom response function');
                    handlerOpts.customResponseFunction = funcResult.customResponseFunction;
                    if (!isHttp) {
                        throw new Error('The "customResponseFunction" option can only be used for HTTP requests.');
                    }
                    if (typeof (handlerOpts.customResponseFunction) !== 'function') {
                        throw new Error('The "customResponseFunction" option must be a function.');
                    }
                }
                if (funcResult && funcResult.proxyHeaders) {
                    this.log(handlerOpts.id, `Using custom proxy headers: ${JSON.stringify(funcResult.proxyHeaders)}`);
                    handlerOpts.proxyHeaders = funcResult.proxyHeaders;
                }
                if (handlerOpts.upstreamProxyUrlParsed) {
                    this.log(handlerOpts.id, `Using upstream proxy ${redactParsedUrl(handlerOpts.upstreamProxyUrlParsed)}`);
                }
                return handlerOpts;
            })
            .finally(() => {
                if (this.prepareRequestFunction) socket.resume();
            });
    }
prepareRequestHandling ( request ) { const handlerOpts = { server : this , id : ++ this . lastHandlerId , srcRequest : request , srcHead : null , trgParsed : null , upstreamProxyUrlParsed : null , } ; this . log ( handlerOpts . id , ` ${ request . method } ${ request . url } ${ request . httpVersion } ` ) ; const { socket } = request ; let isHttp = false ; return Promise . resolve ( ) . then ( ( ) => { if ( request . method === 'CONNECT' ) { handlerOpts . trgParsed = parseHostHeader ( request . url ) ; if ( ! handlerOpts . trgParsed ) { throw new RequestError ( ` ${ request . url } ` , 400 ) ; } this . stats . connectRequestCount ++ ; } else { let parsed ; try { parsed = parseUrl ( request . url ) ; } catch ( e ) { throw new RequestError ( ` ${ request . url } ` , 400 ) ; } if ( ! parsed . protocol ) { throw new RequestError ( 'Hey, good try, but I\'m a HTTP proxy, not your ordinary web server :)' , 400 ) ; } if ( parsed . protocol !== 'http:' ) { throw new RequestError ( ` ${ parsed . protocol } ` , 400 ) ; } handlerOpts . trgParsed = parsed ; isHttp = true ; this . stats . httpRequestCount ++ ; } handlerOpts . trgParsed . port = handlerOpts . trgParsed . port || DEFAULT_TARGET_PORT ; if ( ! this . prepareRequestFunction ) return { requestAuthentication : false , upstreamProxyUrlParsed : null } ; socket . pause ( ) ; const funcOpts = { connectionId : handlerOpts . id , request , username : null , password : null , hostname : handlerOpts . trgParsed . hostname , port : handlerOpts . trgParsed . port , isHttp , } ; const proxyAuth = request . headers [ 'proxy-authorization' ] ; if ( proxyAuth ) { const auth = parseProxyAuthorizationHeader ( proxyAuth ) ; if ( ! auth ) { throw new RequestError ( 'Invalid "Proxy-Authorization" header' , 400 ) ; } if ( auth . type !== 'Basic' ) { throw new RequestError ( 'The "Proxy-Authorization" header must have the "Basic" type.' , 400 ) ; } funcOpts . username = auth . username ; funcOpts . password = auth . password ; } return this . prepareRequestFunction ( funcOpts ) ; } ) . then ( ( funcResult ) => { if ( funcResult && funcResult . requestAuthentication ) { throw new RequestError ( funcResult . failMsg || 'Proxy credentials required.' , 407 ) ; } if ( funcResult && funcResult . upstreamProxyUrl ) { try { handlerOpts . upstreamProxyUrlParsed = parseUrl ( funcResult . upstreamProxyUrl ) ; } catch ( e ) { throw new Error ( ` ${ e } ${ funcResult . upstreamProxyUrl } ` ) ; } if ( ! handlerOpts . upstreamProxyUrlParsed . hostname || ! handlerOpts . upstreamProxyUrlParsed . port ) { throw new Error ( ` ${ funcResult . upstreamProxyUrl } ` ) ; } if ( ! this . supportedProxySchemes . has ( handlerOpts . upstreamProxyUrlParsed . protocol ) ) { throw new Error ( ` ${ funcResult . upstreamProxyUrl } ` ) ; } if ( / : / . test ( handlerOpts . upstreamProxyUrlParsed . username ) ) { throw new Error ( 'Invalid "upstreamProxyUrl" provided: The username cannot contain the colon (:) character according to RFC 7617.' ) ; } } if ( funcResult && funcResult . customResponseFunction ) { this . log ( handlerOpts . id , 'Using custom response function' ) ; handlerOpts . customResponseFunction = funcResult . customResponseFunction ; if ( ! isHttp ) { throw new Error ( 'The "customResponseFunction" option can only be used for HTTP requests.' ) ; } if ( typeof ( handlerOpts . customResponseFunction ) !== 'function' ) { throw new Error ( 'The "customResponseFunction" option must be a function.' ) ; } } if ( funcResult && funcResult . proxyHeaders ) { this . log ( handlerOpts . id , ` ${ JSON . stringify ( funcResult . proxyHeaders ) } ` ) ; handlerOpts . proxyHeaders = funcResult . proxyHeaders ; } if ( handlerOpts . upstreamProxyUrlParsed ) { this . log ( handlerOpts . id , ` ${ redactParsedUrl ( handlerOpts . upstreamProxyUrlParsed ) } ` ) ; } return handlerOpts ; } ) . finally ( ( ) => { if ( this . prepareRequestFunction ) socket . resume ( ) ; } ) ; }
Authenticates a new request and determines upstream proxy URL using the user function.
Authenticates a new request and determines upstream proxy URL using the user function .
// console.log('XXX prepareRequestHandling'); // console.dir(_.pick(request, 'url', 'method')); // console.dir(url.parse(request.url)); // console.dir(_.pick(request, 'url', 'headers', 'method')); // Determine target hostname and port // The request should look like: //   CONNECT server.example.com:80 HTTP/1.1 // Note that request.url contains the "server.example.com:80" part // If srcRequest.url does not match the regexp tools.HOST_HEADER_REGEX // or the url is too long it will not be parsed so we throw error here. // The request should look like: //   GET http://server.example.com:80/some-path HTTP/1.1 // Note that RFC 7230 says: // "When making a request to a proxy, other than a CONNECT or server-wide //  OPTIONS request (as detailed below), a client MUST send the target //  URI in absolute-form as the request-target" // If URL is invalid, throw HTTP 400 error // If srcRequest.url is something like '/some-path', this is most likely a normal HTTP request // Only HTTP is supported, other protocols such as HTTP or FTP must use the CONNECT method // Authenticate the request using a user function (if provided) // Pause the socket so that no data is lost // User function returns a result directly or a promise // If not authenticated, request client to authenticate // eslint-disable-line max-len // eslint-disable-line max-len // eslint-disable-line max-len