HapiJS代理故障

TL; DR

如何拦截一个请求,为一个ID ping不同的路由,将ID存储在一个会话中,然后使用刚刚得到的ID继续原始请求(特别是带有有效载荷的PUT / POST)?

背景

我正在使用HapiJS(8)将来自客户端的请求代理到现有的API(我无法控制进程/逻辑)。 API要求每个请求在查询string或有效负载中包含“会话ID”(取决于http方法)。 为了获得一个会话ID,我所要做的就是要求一个…没有用户名/密码要求(它使用标题中的基本身份validation)。 会话ID如果不更新,则每24小时过期一次。 每个客户都有自己的会话ID。

我目前正在使用hapi-auth-cookie来存储会话ID的值,当需要ID时会被查询。 如果ID过期或为空,我需要请求一个新的客户端的请求才能成功地代理到API。

当前解决scheme

当客户端的请求方法是'GET'时,我正在使用hapi-auth-cookie文档中描述的appendNextconfiguration来非常优雅地处理这个挑战。 这个请求被hapi-auth-cookie拦截,如果需要一个新的会话ID,请求被发送到那个特定的路由来获取它,API返回然后​​分配给Hapi会话的ID,然后(使用Wreck ) reply.redirect返回到完成的原始GET请求。 无缝和优雅。

但是,我不知道如何用不同的包含数据有效载荷的http方法来完成同样的stream程。

除了reply.redirect之外,还有什么能够完成同样的目标,同时保持原始的有效载荷和方法? 还是有更好的方法来做到这一点?

代码(目前适用于“GET”请求的代码)

主要应用程序文件(hapi-auth-cookieconfiguration)

 # register plugins server.register require('./plugins') , (err) -> throw err if err # set authentication (NLS) configs server.auth.strategy 'session', 'cookie', password: 'session_pwd' cookie: 'ghsid' redirectTo: '/api/SessionID' #get new session ID isSecure: config.get 'ssl' appendNext: true ttl: config.get 'session_length' 

使用会话身份validation并调用hapi-auth-cookie插件的控制器:

 simpleRequest: auth: 'session' handler: (request, reply) -> qs = Qs.stringify request.query request.papi_url = "/api/route/sample?#{qs}" reply.proxy mapUri: (request, reply) -> auth = config.get 'basic_auth' api_host = config.get 'api_host' papi_url = request.papi_url path = api_host + papi_url next null, path, {authorization: auth} 

获取新会话ID的路线

 module.exports = [ { path: '/api/SessionID' method: 'GET' config: SessionController.session } ] 

会话控制器

 Wreck = require 'wreck' config = require 'config' module.exports = session: description: 'Get new session ID' auth: mode: 'try' strategy: 'session' plugins: 'hapi-auth-cookie': redirectTo: false handler: (request, reply) -> # request configs papi_url = "/Session" api_host = config.get 'api_host' url = api_host + papi_url opts = headers: 'Authorization': config.get 'basic_auth' 'content-type': 'application/json;charset=UTF-8' # make request to PAPI Wreck.post url, opts, (err, res, body) -> throw new Error err if err try bdy = JSON.parse body sess = nls: bdy.SessionId if bdy.SessionId # authenticate user with NLS request.auth.session.set sess # redirect to initial route reply.redirect request.url.query.next else return throw new Error catch err throw new Error err 

最终解决scheme

基于马特哈里森的答案,我创build了一个自定义插件,作为一个身份validationscheme注册,所以我可以控制这条路线。

这是插件代码:

 Wreck = require 'wreck' config = require 'config' exports.register = (server, options, next) -> server.auth.scheme 'cookie', internals.implementation next() exports.register.attributes = name: 'Hapi Session Interceptor' version: '1.0.0' internals = {} internals.implementation = (server, options, next) -> scheme = authenticate: (request, reply) -> validate = -> session = request.state.sessionID unless session return unauthenticated() reply.continue(credentials: {'session': session}) unauthenticated = -> api_url = "/SessionID" api_host = config.get 'api_host' url = api_host + api_url opts = headers: 'Authorization': config.get 'basic_auth' 'content-type': 'application/json;charset=UTF-8' # make request to API Wreck.post url, opts, (err, res, body) -> throw new Error err if err bdy = JSON.parse body sess = session: bdy.SessionId if bdy.SessionId reply.state 'sessionID', bdy.SessionId reply.continue(credentials: sess) else return throw new Error validate() return scheme 

尽pipe不完全忠于你的代码,但我已经把一个例子和我认为你正在处理的所有东西放在一起。

我已经做了一个service插件来表示你的 API。 upstream插件表示您正在代理的实际上游API。

所有请求都通过service传递,并被代理到upstream ,这只是打印出所有收到的头和有效载荷。

如果原始请求不包含具有sessionId的cookie,则会在upstream访问路由以获取一个。 然后,当响应回落到下游时,Cookie将使用此值设置。

代码在这里: https : //github.com/mtharrison/hapijs-proxy-trouble

试着用curl和你的浏览器。

GET: curl http://localhost:4000

POST W / PAYLOAD: curl -X POST -H "content-type: application/json" -d '{"example":"payload"}' http://localhost:4000

index.js

 var Hapi = require('hapi'); var server = new Hapi.Server(); server.connection({ port: 4000, labels: ['service'] }); // Your service server.connection({ port: 5000, labels: ['upstream']}); // Pretend upstream API server.state('session', { ttl: 24 * 60 * 60 * 1000, isSecure: false, path: '/', encoding: 'base64json' }); server.register([{ register: require('./service') }, { register: require('./upstream') }], function (err) { if (err) { throw err; } server.start(function () { console.log('Started!'); }); }); 

service.js

 var Wreck = require('wreck'); exports.register = function (server, options, next) { // This is where the magic happens! server.select('service').ext('onPreHandler', function (request, reply) { var sessionId = request.state.session; var _done = function () { // Set the cookie and proceed to the route request.headers['X-Session-Id'] = sessionId; reply.state('session', sessionId); reply.continue(); } if (typeof sessionId !== 'undefined') return _done(); // We don't have a sessionId, let's get one Wreck.get('http://localhost:5000/sessionId', {json: true}, function (err, res, payload) { if(err) { throw err; } sessionId = payload.id; _done(); }); }); server.select('service').route({ method: '*', path: '/{p*}', // Proxies all routes and methods handler: { proxy: { host: 'localhost', port: 5000, protocol: 'http', passThrough: true } } }); next(); }; exports.register.attributes = { name: 'your-service' }; 

upstream.js

 exports.register = function (server, options, next) { server.select('upstream').route([{ method: '*', path: '/{p*}', handler: function (request, reply) { // Just prints out what it received for headers and payload // To prove we got send the original payload and the sessionID header reply({ originalHeaders: request.headers, originalPayload: request.payload, }) } }, { method: 'GET', path: '/sessionId', handler: function (request, reply) { // Returns a random session id reply({ id: (Math.floor(Math.random() * 1000)) }); } }]); next(); }; exports.register.attributes = { name: 'upstream' };