Coder Social home page Coder Social logo

h2o2's Introduction

@hapi/h2o2

Proxy handler for hapi.js.

h2o2 is part of the hapi ecosystem and was designed to work seamlessly with the hapi web framework and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out hapi โ€“ they work even better together.

Visit the hapi.dev Developer Portal for tutorials, documentation, and support

Useful resources

h2o2's People

Contributors

achingbrain avatar adisingh007 avatar adrianblynch avatar antony avatar arb avatar avvs avatar bryant1410 avatar cjihrig avatar danielb2 avatar devinivy avatar dkolba avatar eyepulp avatar geek avatar hueniverse avatar jarrodyellets avatar jboutros avatar jmm avatar joshkel avatar kanongil avatar ldesplat avatar lloydbenson avatar marsup avatar matthewcallis avatar nargonath avatar nwhitmont avatar osukaa avatar pgayvallet avatar spanditcaa avatar tuckbick avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

h2o2's Issues

How do I conditionally call a proxy server based on route params?

let post = 
{
  method: 'POST',
  path: '/posts/new/{postType}',
  handler: (request, reply) =>{
       //when params is text
  if (request.params.postType == "text") 
  {
    return reply("successfully uploaded text post");
  }
   //when params is image
  else (request.params.postType == "image") 
  {
    //call proxy uri to save the image
    /*proxy: 
     {
         uri: proxyUri + '/post/image', //call proxy uri to save the image
         passThrough: true,
         acceptEncoding: false,
      }*/
   },
    config: {
        auth: false,
        description: 'add new text-post',
        payload: {
            output: 'stream',
            parse: false,
            allow: 'multipart/form-data'
        }
    }
 }

Call an external API and then continue with processing

Hi,
Can this be used such that in the handler i call an external api and continue in the handler without waiting for a response from that external api. Basically just want to dump some data through an external api without having the handler wait for any response

passing back headers after onResponse

in the example for onResponse, we show some potential payload manipulation/logging after doing a Wreck.read. However, what's not apparent in this example is that all the headers in the original response are lost and only the payload is passed on.

I've tried doing res.payload = payload and doing reply(res) which would have been very handy.

If only logging is done, then it's possible to do:

onResponse: function (res, ....) {
   reply(res);
   Wreck.read(res, payload) {
      // loggging can be done on payload
      return;
   }
}

( thanks @arb for thinking of this )

However, as soon as payload manipulation needs to be done, headers need to be copied. Is there a quick way to copy the headers here?

Perhaps the docs should note that in the onResponse example, the headers are lost.

Make X-Forwarded-Port and X-Forwarded-Proto correct

Hi,

I believe h2o2 is sending the wrong X-Forwarded-Port and X-Forwarded-Proto headers and would like to fix it since it's used by e.g. Kibana.

X-Forwarded-Port

The X-Forwarded-Port request header helps you identify the port that the client used to connect to the load balancer.

http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html#x-forwarded-port

Currently h2o2 is sending the client's source port, for example 43252. X-Forwarded-Port is used to determine if the client used e.g. port 80, 8080 or 443 when connecting.

Single item vs list

X-Forwarded-Proto, X-Forwarded-Port and X-Forwarded-Host shouldn't be lists, at least according to the docs I've found.

Syntax:
X-Forwarded-Proto: <protocol>
from
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
Only X-Forwarded-For should be a list:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For

Pseudocode for how single element items should work:

if (trustForwardheaders && header_exists(XForwardedPort)) {
   Pass existing header
} else {
  Set the port from the url
}

options.headers['x-forwarded-port'] = (options.headers['x-forwarded-port'] ? options.headers['x-forwarded-port'] + ',' : '') + request.info.remotePort;

Adding Headers to request before proxying

I'm not sure if this is possible with h2o2, but I want to proxy a request to an API that requires an API header. I don't want this sitting on the client obviously, so I want to add it to the request's headers before sending on.

Is this possible with h2o2, as I can't see where? Would I have to make changes to the req object as needed and then send it off with wreck or something before replying with the result? In which case h2o2 isn't really needed!

Forward proxy

Is this plugin suitable for a forward proxy?

I ask here because I'm sure one could fudge it and get a proof of concept working, but h2o2 does not seem intended for that purpose. It would be hard for anyone on StackOverflow to correct me about that.

Proxy WebSocket protocol

I am currently using HAPI with the plugin 'hapi-plugin-websocket' to be able to serve websockets. The serving of the websocket requests will be done in microservice other than the main public api therefore i need to proxy the ws requests from the public api to the websocket api but it seems like h2o2 is currently only able to serve http/https requests. Is there any chance to extend the proxy forwarding to ws:// protocol?

mapUri can't return a NON error response without call upstream

Hi, I wanna know if you have a plan to return a reply response when some business logic detect an issue on mapUri and should return a response before send it to upstream.

I make a fork with a possible solution, but if you have a bit of time to give me some best idea or work on it let me know to help you.

I only pass reply interface on mapUri. It have a way to check if was already called to prevent errors.

Let me show:

server.route({
  method: 'GET',
  path: '/foo',
  handler: {
    proxy: {
      passThrough: true,
      acceptEncoding: false,
      xforward: true,
      mapUri: function(request, callback){ // you maybe can pass reply interface here to this case... 
        if(foo === 'alreadyExistHere'){
          // ? plan to reply({text: 'alreadyExistHere'}).code(200);
        }else{
          callback(null, 'http://upstream.server/getSomething');
        }
      },
      onResponse: function(error, res, req, reply, settings, ttl){
        // this code is only as example to explain myself. Here we save something in local cache to 
        // prevent ask upstream again and only return this value next time. (business logic)
        if(!foo){
          foo = resp.body;
        }
        reply(resp);
      }
    }
  }
})

Call settings.mapUri with `h` as the second argument

I have a use case where mapUri cannot be completed and need to call h.unstate from my mapUri handler. Would it be possible to pass in h along with the request? I tried this by editing the file in node_modules and it worked perfectly for my needs.

const { uri, headers } = await settings.mapUri(request);

For symmetry, I would also include h to calls to onRequest and onResponse.

how can i get the request.payload in onResponse

I wanna get the parsed post payload in onResponse by below:

onResponse: function (err, response, request, reply) {
    console.log(request.payload);
}

but which is not the real post params, how can I get that in onResponse

Accessing body POST parameters in proxy handler

Goal: On some routes, the POST parameters need to be modified before they are forwarded. I can access query parameters (e.g. ?param1=x&param2=y) but I haven't been able to find where the in-body POST parameters are. Here's what I have that manipulates the query params:

server.route({
    method: "POST",
    path: "/myEndpoint",
    handler: {
        proxy: {
            mapUri: function (request, callback) {
                request.query.id = "Some new ID";

                // request.params is empty
                // request.payload doesn't appear to have the POST parameters

                // Call proxied host with modified query parameters.
                // Would prefer to send parameters in body instead of url.
                callback(null, "http://proxiedHost" + request.path + "?" + qs.stringify(request.query));
            }
        }
    }
});

How may I access and manipulate the body POST parameters?

How to route to a new path?

What would be the appropriate way of proxying traffic from:

domain.com/api/v3/something

to

anotherdomain.com/something

?

I'm using this but of course h2o2 is sending the complete path to anotherdomain.com.

		{
			method: '*',
			path: '/{p?}',
			handler: {
				proxy: {
					host: 'anotherdomain.com',
					passThrough: true,
					xforward: true,
					port: 443,
					protocol: 'https'
				}
			}
		}

Proxing changes cache-control from public to private

I am trying to proxy an endpoint with hapi that provides a cache control header (max-age=30, must-revalidate, public). When I specify a simple onResponse handler with reply(res) (see below) all headers are passed correctly. Removing this handler and letting hapi do the work, results in a cache-control header of no-cache.

This was expected. I need to specify ttl: "upstream" in the route config, to pass along the cache headers. But then the cache-control header changes from public to private: 'max-age=30, must-revalidate, private'``

Dumb onResponseHandler:

function (err, res, req, reply, settings, ttl) {
    reply(res);
};

Can't proxy options requests

h2o2 is unable to proxy any requests with method "OPTIONS"

Steps to reproduce:

Register a route with the following settings:

method: 'OPTIONS',
path: '/test',
handler: function (request, h) {
  return h.proxy({
    host: 'http://google.com'
  });
}

Run the server and make an "OPTIONS" request to the path specified.
Receive the error message:
AssertionError [ERR_ASSERTION]: Cannot proxy if payload is parsed or if output is not stream or data at Object.internals.handler (/var/bonfire-api/node_modules/h2o2/lib/index.js:78:10)
It seems like Hapi automatically sets the payload for OPTIONS requests to

{
  output: 'data',
  parse: true
}

Is it possible to handle OPTIONS routes as a separate case?

mapUri mis-setting server name for SNI

We're using h2o2 to act as a reverse proxy and forward some requests, but we found we needed to filter out some headers on the request, so we moved from the uri parameter to the mapUri parameter.

After pulling out the packet sniffer, it seems like when we switch to mapUri, the request is being made with localhost as the server name during the TLS setup / SNI stage. This does not appear to be happening when we use the uri parameter (in fact, we seem to get the correct server name then), despite passing in the same information.

New maintainer

This module needs a new maintainer to keep it up to hapi.js standards.

Error when using mapUri

I register a simple proxy handler:

  {
    method: 'GET',
    path: '/_api/{variables}',
    handler: {
      proxy: {
        passThrough: true,
        mapUri: (request, callback) => {
          callback(null, baseUri + request.params.variables);
        }
      }
    }
  }

This leads to the following error:

Debug: internal, implementation, error
    Error: Uncaught error: options.payload must be a string, a Buffer, or a Stream
    at Object.exports.assert (/Users/robert/Entwicklung/my-project/node_modules/hoek/lib/index.js:736:11)
    at internals.Client.request (/Users/robert/Entwicklung/my-project/node_modules/wreck/lib/index.js:75:10)
    at settings.mapUri (/Users/robert/Entwicklung/my-project/node_modules/h2o2/lib/index.js:140:19)
    at Object.mapUri (/Users/robert/Entwicklung/my-project/src/routes/api.js:21:11)

As far as I found out, h2o2 creates an options object, but wreck requires it to not be an object. Is this intentional?

onResponse - Does h2o2 still use the reply interface?

In the Readme, the onResponse function has the signature (err, res, request, reply, settings, ttl), with reply being the reply interface function. Hapi v17 doesn't have this interface anymore and instead uses h, the response toolkit.

Defaults aren't being applied to settings before validating handlerOptions

On server.register I'm passing default options such as host, protocol, port, so one would assume that they would be set. But when I invoke reply.proxy I'm getting joi validation issues.

It's late so I haven't spent too much time looking at the logic, but I'd imagine this is the culprit

   Joi.assert(handlerOptions, internals.schema, 'Invalid proxy handler options (' + route.path + ')');
   Hoek.assert(!route.settings.payload || ((route.settings.payload.output === 'data' || route.settings.payload.output === 'stream') && !route.settings.payload.parse), 'Cannot proxy if payload is parsed or if output is not stream or data');
   const settings = Hoek.applyToDefaultsWithShallow(internals.defaults, handlerOptions, ['agent']);

I think this is what we want...

   const settings = Hoek.applyToDefaultsWithShallow(internals.defaults, handlerOptions, ['agent']);
   Joi.assert(settings, internals.schema, 'Invalid proxy handler options (' + route.path + ')');
   Hoek.assert(!route.settings.payload || ((route.settings.payload.output === 'data' || route.settings.payload.output === 'stream') && !route.settings.payload.parse), 'Cannot proxy if payload is parsed or if output is not stream or data');
   

sending query parameters through

can you explain how to proxy query parameters, or is this not supported functionality. i have passthrough: true and i'm using mapURI

method: ['POST','GET', 'DELETE'], path:'/jaya/{p*}', handler: { proxy: { passThrough: true, mapUri: function(request, callback){ if(request.params.p) callback(null, 'http://localhost:82/' + request.params.p); else callback(null, 'http://localhost:82/'); } } } },

Default options do not apply when using reply.proxy(options)

Hello,

I noticed that when you use reply.proxy(options) to proxy request, the options set when registering the plugin do not apply.

Here is how I register the plugin:

        // PROXY
        {
            plugin: {
                register: 'h2o2',
                options: {
                    passThrough: true,
                    protocol: 'http'
                }
            }
        },

And here is how I use it

handler(request,reply){

            reply.proxy( {
                passThrough: true,
                uri: `${RESULTS_SERVICE}/results/{definitionSource}/{program}${request.url.search}`
            } );
        }

The settings apply if I use the handler: { proxy : {} } form.
Can the default options be applied also to the reply.proxy method? Is a bit uncomfortable to define all the default options again and again on every proxy request.

Thanks and regards

Document reply.proxy

reply.proxy(options)

Proxies the request to an upstream endpoint where:

No return value.

The response flow control rules do not apply.

var handler = function (request, reply) {

    return reply.proxy({ host: 'example.com', port: 80, protocol: 'http' });
};

Cannot proxy if payload is parsed

 plugin.route({
     method: 'POST',
     path: '/item',
     handler: function (request, reply) {

         reply.proxy({ host: 'www.google.com', port: 80 });
     }
 });

results in an error: Cannot proxy if payload is parsed or if output is not stream or data

While:

   plugin.route({
       method: 'POST',
       path: '/item',
       handler: {
           proxy: {
               host: 'www.google.com', port: 80
           }
       }
   });

works as expected.

Is the former really considered as parsing payload?

https://github.com/hapijs/h2o2/blob/master/lib/index.js#L71

route.settings.payload in this context shows up as null

Wreck instrumentation

Hi,

We have currently an API platform based on HapiJS (v16.5.2), using good (v7.3.0) and wreck (v10.0.0).
This stack allows use a lot of logs based on good and the support of wreck events.
So the monitoring/instrumentation is ease.

With the lastest version, the wreck event are no more supported from hapi/good (hapijs/good#568).

Do you thinks it's possible to expose the wreck configuration accessible from the h2o2 package configuration ?

I'm pretty new in NodeJs, so if I miss somethings don't hesitate to fix me ๐Ÿ˜„


My workaround is to duplicate the reverse proxy mechanism in h2o2 and add the following code to activate the wreck log mechanism.

Configuration of wreck event :

const Wreck = require('wreck').defaults({ events: true });

Subscribe to wreck event (in this case, the response)

Wreck.events.on('response', handler);

Register a new event named custom in Hapi server

server.event('custom');

Raised a custom event server in the wreck handler

const handler = async (err, details) => {

    if (err) {
      throw err;
    }

    // Based on good source code https://github.com/hapijs/good/tree/v7.3.0
    await server.events.emit('custom', new WreckResponse(err, details.req, details.res, details.start, details.uri));
};

How to add auth to the destination?

How do you apply authorization to the outbound call? For Basic Auth could use mapUri, but for other authorization types this seems to be overlooked (at least in the docs).

How to proxy EventSource requests.

I'm using EventSource on the frontend to an endpoint which uses Susie. I have a proxy sitting in between the frontend and my node app. When Wreck (inside of h2o2) sends the request, it doesn't return anything, it just sits there with the request open. Any tips to get this working?

Allow payload to be parsed when proxying

I am building a microservices api gateway which basically proxies the requests to different services.

I would like to be able to validate the request payload before proxying. This is mainly because the api gateway might accept a subset of the service payload (for example a public api gateway will allow only to submit just a few fields for a certain service, and no the whole payload).

Currently in order to validate the payload, it is required to set the payload parse to true. However the proxy prevents parsing the payload.

Related to hapijs/hapi#2260

Incorrect handling of http to https redirects

I'm still debuging this, but here's what I know so far:

  • I have a thing that proxies to http://drive.google.com/uc?id=xxx, which in turn redirects to an https URL.
  • It used to work with [email protected]
  • It doesn't work with [email protected]
  • It works if I proxy to https://drive.google.com/uc?id=xxx
  • Under node 0.10 the error message is Client request error: socket hang up, under node 0.12 the message is Uncaught error: Protocol "https:" not supported. Expected "http:"..

Hapi 8.6 uses:

Hapi 8.8 uses:

I can make things work if:

https Error: tunneling socket could not be established, cause=socket hang up

I'm trying to implement forward proxy and it works ok for http requests, but for https it says:

Error: tunneling socket could not be established, cause=socket hang up
    at ClientRequest.onError (/Users/zak/dev/proxy/node_modules/tunnel-agent/index.js:176:17)
    at ClientRequest.g (events.js:286:16)
    at emitOne (events.js:96:13)
    at ClientRequest.emit (events.js:188:7)
    at Socket.socketOnEnd (_http_client.js:344:9)
    at emitNone (events.js:91:20)
    at Socket.emit (events.js:185:7)
    at endReadableNT (_stream_readable.js:975:12)
    at _combinedTickCallback (internal/process/next_tick.js:74:11)
    at process._tickCallback (internal/process/next_tick.js:98:9)

Here is an example of code (node 6). Just replace
const url = 'http://www.example.com'
with
const url = 'https://www.example.com'
to see the error

const h2o2    = require('h2o2')
const Hapi    = require('hapi')
const request = require('request')

const server = new Hapi.Server()
server.connection({ host:'localhost', port:3001 })

server.register([{ register:h2o2 }], function(err) {
  server.start(function(err) {
    if (err) return console.error(err)
    console.log('started at', server.info.uri)
    fetchUsingThisProxy()
  })
})

server.route({
  method  : '*',
  path    : '/fwd/{path*}',
  handler : (req, res) => {
    res('FORWAREDED')
  }
})

server.route({
  method  : '*',
  path    : '/{path*}',
  handler : {
    proxy : {
      passThrough    : true,
      acceptEncoding : false,
      timeout        : 15000,
      redirects      : 10,
      uri            : 'http://localhost:3001/fwd',
      onResponse (err, res, request, reply, settings, ttl) {
        reply(res)
      }
    }
  }
})

function fetchUsingThisProxy() {
  const url   = 'http://www.example.com'
  const proxy = server.info.uri
  const opts  = { url, proxy }

  return request(opts, (err, response, body) => {
    if (err) throw err
    console.log(body)
  })
}

feature request: upstreamPassThrough only

problem

passThrough passes through bi-directionly. localPassThrough passes state from local => upstream. i need the inverse of localPassThrough

discussion

is something you'd be willing to support?

perhaps the API could be simplified to passThrough = 'downstream' | 'upstream' | 'bidirectional' or something similar?

proxy server doesn't receive the post params.

when i set hapijs's request proxy the request to my proxy server,my proxy server never receive the params ,why ?

here is the code

server.route({
method: ['GET','PUT','POST','DELETE'],
path: '/atyun/{any*}',
handler: {
proxy: {
host: '127.0.0.1',
port: '3000',
protocol: 'http'
}
}
});

Any way to integrate with catbox?

I'd like to be able cache (via catbox-redis) our proxied content. I don't see a way to short-circuit the proxy route handler though. Right now I'm using mapUri and onResponse, as we do a lot of manipulation to the proxied content.

Any suggestions?

How to change response headers

Normally when you reply(json), you can do .header(key, value) afterwards.

How can you change headers, if you respond with a return reply.proxy()?

Because return reply.proxy({}).header(key, value) does not work.

Is there a way to implement this inside a handler?

Hi, I'm looking a way to modify the payload of a request and then send it to the upstream server. After checking the implementation in hapijs is not too clear if I can do this without completely replacing the handler but instead doing a call to h2o2's handler inside the route handler.

Any guiding on this would be highly appreciated.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.