h2o2 Proxy Handler

repository·master·Indexed 41 days ago

https://github.com/hapijs/h2o2

A proxy handler plugin for hapi.js (version 10.0.4) that enables proxying capabilities for web applications. It allows routing requests to upstream endpoints using host, port, and protocol, absolute URIs with template variables, or dynamic mapping via mapUri and onResponse. While designed for the hapi ecosystem, it is a standalone module compatible with other web frameworks.

Tokens
4.1K
Snippets
13
Records
15
Agent score
51%

What's inside h2o2

  1. Overview of @hapi/h2o2

    master
    @hapi/h2o2 is a proxy handler designed for the hapi.js web framework. While it is part of the hapi ecosystem and integrates seamlessly with hapi and its other components, it is a standalone module that can also be used with other web frameworks.
  2. Use template values in the uri option

    master

    When using the uri option, you can inject values from the incoming request using the following template variables:

    • {protocol}
    • {host}
    • {port}
    • {path}
    • {query}

    You can also capture request.params and query values by using a similar replacement strategy. Note that the default variables listed above take precedence; avoid using them as names for your own request.params.

    // Example using default template values
    server.route({
        method: 'GET',
        path: '/foo',
        handler: {
            proxy: {
                uri: '{protocol}://{host}:{port}/go/to/{path}'
            }
        }
    });
    
    // Example using request.params
    server.route({
        method: 'GET',
        path: '/foo/{bar}',
        handler: {
            proxy: {
                uri: 'https://some.upstream.service.com/some/path/to/{bar}{query}'
            }
        }
    });
  3. Install and register @hapi/h2o2

    master

    To add proxying functionality to your hapi server, register the @hapi/h2o2 plugin. This allows you to use the proxy handler in your route configurations.

    const Hapi = require('@hapi/hapi');
    const H2o2 = require('@hapi/h2o2');
    
    const start = async function() {
    
      const server = Hapi.server();
    
      await server.register(H2o2);
      await server.start();
    
      console.log(`Server started at:  ${server.info.uri}`);
    };
    
    start();
  4. Configure URI mapping with templates

    master

    When defining the uri option, you can use placeholders that h2o2 will automatically replace using the current request's information:

    • {protocol}: The current server's protocol.
    • {host}: The current server's host.
    • {port}: The current server's port.
    • {path}: The current request's path.
    • {query}: The current request's query string.
    • {key}: Any parameter defined in the route (e.g., {id}).

    Example: uri: 'https://api.example.com/{path}' will map /users to https://api.example.com/users.

    // If the route is /user/{id}
    // And the request is GET /user/123?name=foo
    // The following uri will map to: https://upstream.com/api/123?name=foo
    h.proxy({
        uri: 'https://upstream.com/api/{id}{query}'
    });
  5. Register the @hapi/h2o2 plugin

    master

    To use h2o2 as a proxy in your hapi server, register the plugin. You can provide global configuration options during registration which will be applied to all proxy requests unless overridden at the route level.

    Global options include:

    • host, port, protocol, uri: Define the upstream target.
    • passThrough: If true, headers from the downstream request are passed to the upstream (excluding host and content-length).
    • localStatePassThrough: If true, allows cookies defined by the upstream server to be passed back to the client.
    • redirects: Number of redirects to follow (or false).
    • timeout: Request timeout in milliseconds (default is 3 minutes).
    • maxSockets: Limit on the number of sockets per agent.
    • xforward: If true, adds x-forwarded-* headers to the upstream request.
    • ttl: If set to 'upstream', the proxy will use the max-age from the upstream's Cache-Control header to set the response TTL.
    const Hapi = require('@hapi/hapi');
    const H2o2 = require('@hapi/h2o2');
    
    const init = async () => {
        const server = Hapi.server();
    
        await server.register({
            plugin: H2o2,
            options: {
                host: 'upstream-server.com',
                protocol: 'https'
            }
        });
    
        await server.start();
    };
  6. Use a custom HTTP client

    master

    By default, h2o2 uses wreck. You can provide a custom client by passing an object to the httpClient option. The client must implement the wreck interface, specifically the request() and parseCacheControl() methods.

    server.route({
        method: 'GET',
        path: '/',
        handler: {
            proxy: {
                httpClient: {
                    request(method, uri, options) {
                        return axios({
                            method,
                            url: 'https://some.upstream.service.com/'
                        })
                    }
                }
            }
        }
    });
  7. Proxy to an absolute URI

    master

    Use the uri option to send requests to a specific absolute URI. This overrides the incoming host, port, protocol, path, and query. This configuration cannot be used simultaneously with host, port, protocol, or mapUri.

    server.route({
        method: 'GET',
        path: '/',
        handler: {
            proxy: {
                uri: 'https://some.upstream.service.com/that/has?what=you&want=todo'
            }
        }
    });
  8. Configure proxy using host, port, and protocol

    master

    You can direct requests to a specific upstream service by providing the host, port, and protocol (either 'http' or 'https'). The request will be sent to the upstream service using the same path as the original client request. This configuration cannot be used simultaneously with uri or mapUri.

    server.route({
        method: 'GET',
        path: '/',
        handler: {
            proxy: {
                host: '10.33.33.1',
                port: '443',
                protocol: 'https'
            }
        }
    });
  9. Map URIs and process responses with mapUri and onResponse

    master

    For advanced proxying, use mapUri and onResponse together. This combination allows you to dynamically determine the upstream URI and manipulate the response before it reaches the client. This cannot be used with host, port, protocol, or uri.

    mapUri

    Signature: function (request) Returns an object:

    • uri: The absolute proxy URI.
    • headers: (Optional) An object of HTTP request headers to send.

    onResponse

    Signature: function (err, res, request, h, settings, ttl)

    • err: Internal or upstream error.
    • res: The Node response object (a readable stream). You are responsible for closing this stream.
    • request: The incoming request object.
    • h: The response toolkit.
    • settings: The proxy handler configuration.
    • ttl: The upstream TTL in milliseconds (if ttl is set to 'upstream').
    server.route({
        method: 'GET',
        path: '/',
        handler: {
            proxy: {
                mapUri: function (request) {
    
                    console.log('doing some additional stuff before redirecting');
                    return {
                        uri: 'https://some.upstream.service.com/'
                    };
                },
                onResponse: async function (err, res, request, h, settings, ttl) {
    
                    console.log('receiving the response from the upstream.');
                    const payload = await Wreck.read(res, { json: true });
    
                    console.log('some payload manipulation if you want to.');
                    const response = h.response(payload);
                    response.headers = res.headers;
                    return response;
                }
            }
        }
    });
  10. Proxy requests using h.proxy(options)

    master

    You can proxy a request to an upstream endpoint within a route handler using h.proxy(options). The options object accepts the same keys as the route configuration proxy handler. Note that response flow control rules do not apply when using this method.

    const handler = function (request, h) {
    
        return h.proxy({ host: 'example.com', port: 80, protocol: 'http' });
    };
  11. Configure proxy handler options

    master

    The following options can be provided when registering the plugin or via h.proxy(options) to configure the proxy behavior:

    OptionDescription
    hostUpstream service host. Path is appended from client request.
    portUpstream service port.
    protocol'http' or 'https'.
    uriAbsolute URI. Cannot be used with host, port, protocol, or mapUri.
    httpClientAn HTTP client abiding by the Wreck interface. Defaults to wreck.
    passThroughIf true, forwards client headers to upstream and upstream headers to client. Defaults to false.
    localStatePassThroughIf false, removes locally defined state from incoming requests. Defaults to false.
    acceptEncodingIf false, does not pass-through 'Accept-Encoding' header. Useful for onResponse processing. Defaults to true.
    rejectUnauthorizedSets rejectUnauthorized on the HTTPS agent. Defaults to true.
    xforwardIf true, sets X-Forwarded-For, X-Forwarded-Port, X-Forwarded-Proto, and X-Forwarded-Host headers. Defaults to false.
    redirectsMax number of HTTP redirections to follow. false or 0 disables them. Defaults to false.
    timeoutMilliseconds before aborting upstream request. Defaults to 180000.
    mapUriFunction (request) => { uri, headers } to map request to proxy URI.
    onRequestCustom function (req) passed the Wreck request.
    onResponseCustom function (err, res, request, h, settings, ttl) for response processing.
    ttlIf 'upstream', applies upstream caching policy.
    agentNode http(s) agent for connections.
    maxSocketsMax sockets per outgoing host. Defaults to Infinity.
    secureProtocolTLS flag for SSL method (e.g. SSLv3_method).
    ciphersTLS ciphers list to override defaults.
    downstreamResponseTimeIf true, logs time spent processing downstream request. Defaults to false.
  12. Handle upstream responses with onResponse

    master

    You can provide an onResponse callback in your proxy options to intercept the upstream response. This is useful for custom error handling or transforming the response before it is sent to the downstream client.

    The callback receives: (err, res, request, h, settings, ttl).

    • err: The error object if the request failed.
    • res: The upstream response object.
    • request: The downstream request object.
    • h: The hapi toolkit.
    • settings: The effective proxy settings.
    • ttl: The calculated TTL (if ttl: 'upstream' was used).
    h.proxy({
        uri: 'https://api.example.com',
        onResponse: (err, res, request, h, settings, ttl) => {
            if (err) {
                return h.response({ error: 'Upstream unavailable' }).code(502);
            }
            // Custom logic for successful responses
            return h.response(res).ttl(ttl);
        }
    });