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文档中描述的appendNext
configuration来非常优雅地处理这个挑战。 这个请求被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' };