npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@mashroom/mashroom-http-proxy

v2.6.1

Published

Mashroom http proxy service

Downloads

176

Readme

Mashroom HTTP proxy

Plugin for Mashroom Server, a Microfrontend Integration Platform.

This plugin adds a service for forwarding requests to a target URI. It supports HTTP, HTTPS and WebSockets (only the default/nodeHttpProxy implementation, see below).

Usage

If node_modules/@mashroom is configured as plugin path just add @mashroom/mashroom-http-proxy as dependency.

After that you can use the service like this:

import type {MashroomHttpProxyService} from '@mashroom/mashroom-http-proxy/type-definitions';

export default async (req: Request, res: Response) => {
    const httpProxyService: MashroomHttpProxyService = req.pluginContext.services.proxy.service;

    const targetURI = 'http://foo.bar/api/test';
    const additionalHeaders = {};
    await httpProxyService.forward(req, res, targetURI, additionalHeaders);
}

You can override the default config in your Mashroom config file like this:

{
    "plugins": {
        "Mashroom Http Proxy Services": {
            "forwardMethods": [
                "GET",
                "POST",
                "PUT",
                "DELETE"
            ],
            "forwardHeaders": [
              "accept",
              "accept-*",
              "range",
              "expires",
              "cache-control",
              "last-modified",
              "content-*",
              "x-forwarded-*",
              "uber-trace-id",
              "uberctx-",
              "b3",
              "x-b3-*",
              "trace*",
              "sec-websocket-*"
            ],
            "rejectUnauthorized": true,
            "poolMaxTotalSockets": null,
            "poolMaxSocketsPerHost": 10,
            "poolMaxWaitingRequestsPerHost": null,
            "socketTimeoutMs": 30000,
            "keepAlive": true,
            "retryOnReset": true,
            "wsMaxConnectionsTotal": 2000,
            "wsMaxConnectionsPerHost": null,
            "proxyImpl": "default"
        }
    }
}
  • forwardMethods: The HTTP methods that should be forwarded
  • forwardHeaders: The HTTP headers that should be forwarded. May contain a * as wildcard.
  • rejectUnauthorized: Reject self-signed certificates (Default: true)
  • poolMaxTotalSockets: Max HTTP pool sockets total (Default: null - no limit)
  • poolMaxSocketsPerHost: Max HTTP pool sockets per target host (Default: 10)
  • poolMaxWaitingRequestsPerHost: Max waiting HTTP requests per target host, needs to be > 0 if set (Default: null - no limit)
  • socketTimeoutMs: HTTP socket timeout, which is the time the target has to accept the connection and start sending the response (Default: 30000)
  • keepAlive: HTTP connection keep-alive. Set this to false if you experience random ECONNRESET with the nodeHttpProxy implementation, see: https://github.com/nonblocking/mashroom/issues/77 (Default: true)
  • retryOnReset: If the target resets the HTTP connection (because a keep-alive connection is broken) retry once (Default: true)
  • wsMaxConnectionsTotal: Max WebSocket connections total. Set this to 0 if you want to disable the WS proxy (Default: 2000)
  • wsMaxConnectionsPerHost: Max WebSocket connections per target host (Default: 0 - no limit)
  • proxyImpl: Switch the proxy implementation. Currently available are:
    • streamAPI (based on the Node.js stream API)
    • nodeHttpProxy (based on node-http-proxy)
    • default (which is streamAPI)

Services

MashroomHttpProxyService

The exposed service is accessible through pluginContext.services.proxy.service

Interface:

export interface MashroomHttpProxyService {

    /**
     * Forwards the given request to the targetUri and passes the response from the target to the response object.
     * The Promise will always resolve, you have to check response.statusCode to see if the transfer was successful or not.
     * The Promise will resolve as soon as the whole response was sent to the client.
     */
    forward(req: Request, res: Response, targetUri: string, additionalHeaders?: HttpHeaders): Promise<void>;

    /**
     * Forwards a WebSocket request (ws or wss).
     * The passed additional headers are only available at the upgrade/handshake request (most WS frameworks allow you to access it).
     */
    forwardWs(req: IncomingMessageWithContext, socket: Socket, head: Buffer, targetUri: string, additionalHeaders?: HttpHeaders): Promise<void>;

}

Plugin Types

http-proxy-interceptor

This plugin type can be used to intercept http proxy calls and to add for example authentication headers to backend calls.

To register your custom http-proxy-interceptor plugin add this to package.json:

{
    "mashroom": {
        "plugins": [
            {
                "name": "My Custom Http Proxy Interceptor",
                "type": "http-proxy-interceptor",
                "bootstrap": "./dist/mashroom-bootstrap.js",
                "defaultConfig": {
                    "order": 500,
                    "myProperty": "foo"
                }
            }
        ]
    }
}
  • defaultConfig.order: The weight of the middleware in the stack - the higher it is the later it will be executed (Default: 1000)

The bootstrap returns the interceptor:

import type {MashroomHttpProxyInterceptorPluginBootstrapFunction} from '@mashroom/mashroom-http-proxy/type-definitions';

const bootstrap: MashroomHttpProxyInterceptorPluginBootstrapFunction = async (pluginName, pluginConfig, pluginContextHolder) => {

    return new MyInterceptor(/* ... */);
};

export default bootstrap;

The provider has to implement the following interface:

interface MashroomHttpProxyInterceptor {


    /**
     * Intercept request to given targetUri.
     *
     * The existingHeaders contain the original request headers, headers added by the MashroomHttpProxyService client and the ones already added by other interceptors.
     * The existingQueryParams contain query parameters from the request and the ones already added by other interceptors.
     *
     * clientRequest is the request that shall be forwarded. DO NOT MANIPULATE IT. Just use it to access "method" and "pluginContext".
     *
     * Return null or undefined if you don't want to interfere with a call.
     */
    interceptRequest?(targetUri: string, existingHeaders: Readonly<HttpHeaders>, existingQueryParams: Readonly<QueryParams>,
                      clientRequest: Request, clientResponse: Response):
        Promise<MashroomHttpProxyRequestInterceptorResult | undefined | null>;

    /**
     * Intercept WebSocket request to given targetUri.
     *
     * The existingHeaders contain the original request headers, headers added by the MashroomHttpProxyService client and the ones already added by other interceptors.
     *
     * The changes are ONLY applied to the upgrade request, not to WebSocket messages.
     *
     * Return null or undefined if you don't want to interfere with a call.
     */
    interceptWsRequest?(targetUri: string, existingHeaders: Readonly<HttpHeaders>, clientRequest: IncomingMessageWithContext):
        Promise<MashroomWsProxyRequestInterceptorResult | undefined | null>;

    /**
     * Intercept response from given targetUri.
     *
     * The existingHeaders contain the original request header and the ones already added by other interceptors.
     * targetResponse is the response that shall be forwarded to the client. DO NOT MANIPULATE IT. Just use it to access "statusCode" an such.
     *
     * Return null or undefined if you don't want to interfere with a call.
     */
    interceptResponse?(targetUri: string, existingHeaders: Readonly<HttpHeaders>, targetResponse: IncomingMessage,
                       clientRequest: Request, clientResponse: Response):
        Promise<MashroomHttpProxyResponseInterceptorResult | undefined | null>;

}

Examples

Add a Bearer token to each request like this (implemented like this in the mashroom-http-proxy-add-id-token module):

export default class MyInterceptor implements MashroomHttpProxyInterceptor {

    async interceptRequest(targetUri: string, existingHeaders: Readonly<HttpHeaders>, existingQueryParams: Readonly<QueryParams>,
                         clientRequest: Request, clientResponse: Response) {
        const logger = clientRequest.pluginContext.loggerFactory('test.http.interceptor');
        const securityService = clientRequest.pluginContext.services.security && clientRequest.pluginContext.services.security.service;

        const user = securityService.getUser(clientRequest);
        if (!user) {
           return;
        }

        return {
           addHeaders: {
              Authorization: `Bearer ${user.secrets.idToken}`
           }
        };
    }
}

Return forbidden for some reason:

export default class MyInterceptor implements MashroomHttpProxyInterceptor {

    async interceptRequest(targetUri: string, existingHeaders: Readonly<HttpHeaders>, existingQueryParams: Readonly<QueryParams>,
                         clientRequest: Request, clientResponse: Response) {

        clientResponse.sendStatus(403);

        return {
          responseHandled: true
        };
    }
}

Handle the response instead of the proxy:

export default class MyInterceptor implements MashroomHttpProxyInterceptor {

    async interceptResponse(targetUri: string, existingHeaders: Readonly<HttpHeaders>, targetResponse: IncomingMessage, clientRequest: ExpressRequest, clientResponse: ExpressResponse) {
        let body = [];
        targetResponse.on('data', function (chunk) {
            body.push(chunk);
        });
        targetResponse.on('end', function () {
            body = Buffer.concat(body).toString();
            console.log("Response from proxied server:", body);
            clientResponse.json({ success: true });
        });

        // NOTE: if you "await" the end event you have to call targetResponse.resume() here
        //  because the interceptor pauses the stream from the target until all interceptors are done

        return {
            responseHandled: true
        };
    }
}

Compress request/response body (only supported by the default/streamAPI proxy implementation):

import zlib from 'zlib';

export default class MyInterceptor implements MashroomHttpProxyInterceptor {

    async interceptRequest(targetUri) {
        if (targetUri.startsWith('https://my-backend-server.com')) {
            return {
                addHeaders: {
                    'content-encoding': 'gzip',
                },
                streamTransformers: [
                    zlib.createGzip(),
                ],
            };
        }
    }
    
    async interceptResponse(targetUri, existingHeaders) {
        if (targetUri.startsWith('https://my-backend-server.com') && existingHeaders['content-encoding'] === 'gzip') {
            return {
                removeHeaders: [
                    'content-encoding',
                ],
                streamTransformers: [
                    zlib.createGunzip(),
                ],
            };
        }
    }
}

Encrypt request/response body (only supported by the default/streamAPI proxy implementation):

import crypto from 'crypto';

export default class MyInterceptor implements MashroomHttpProxyInterceptor {

    async interceptRequest(targetUri) {
        if (targetUri.startsWith('https://my-backend-server.com')) {
            return {
                streamTransformers: [
                    crypto.createCipheriv(/* .... */),
                ],
            };
        }
    }
    
    async interceptResponse(targetUri, existingHeaders) {
        if (targetUri.startsWith('https://my-backend-server.com')) {
            return {
                streamTransformers: [
                    crypto.reateDecipheriv(/* .... */),
                ],
            };
        }
    }
}