HapiJS 代理问题
HapiJS Proxy Trouble
TL;DR
我如何拦截一个请求,ping 一个 ID 的不同路由,将 ID 存储在 session,然后使用我的 ID 继续原始请求(尤其是 PUT/POST 和负载)刚拿到?
背景
我正在使用 HapiJS (8) 将来自客户端的请求代理到现有的 API(我无法控制 processes/logic)。 API 要求每个请求在查询字符串或负载中包含一个 'session ID'(取决于 http 方法)。为了获得一个 session ID,我所要做的就是索要一个……不需要 username/pwd(它在 headers 中使用基本身份验证)。如果不更新,session ID 每 24 小时过期一次。每个客户都有自己的 session ID。
我目前正在使用 hapi-auth-cookie 来存储 session ID 的值,在需要 ID 时会查询该值。如果 ID 已过期或为空,我需要在客户端请求成功代理到 API.
之前请求一个新 ID
当前解决方案
当客户端的请求方法是 'GET' 时,我正在利用 hapi-auth-cookie docs. The request is intercepted by hapi-auth-cookie, if a new session ID is needed, a request is sent to that particular route to get it, the API returns the ID which is then assigned to the Hapi session, and then (using Wreck) a reply.redirect
[=66 中描述的 appendNext
配置非常优雅地处理这个挑战=] 到它完成的原始 GET 请求。无缝优雅。
但是,我不知道如何使用包含数据有效负载的不同 http 方法来完成相同的流程。
除了 reply.redirect
之外,还有什么东西可以在保持原始有效负载和方法的同时实现相同的目标吗?或者一般来说有更好的方法来做到这一点吗?
代码(目前适用于 'GET' 个请求)
主应用程序文件(hapi-auth-cookie 配置)
# 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'
利用 session 身份验证并调用 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}
获取新 session ID 的路径
module.exports = [
{
path: '/api/SessionID'
method: 'GET'
config: SessionController.session
}
]
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
最终解决方案
根据 Matt Harrison 的回答,我创建了一个自定义插件,该插件被注册为身份验证方案,因此我可以按路由控制它。
插件代码如下:
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
虽然不完全忠实于您的代码,但我已经整理了一个示例,其中包含我认为您正在使用的所有部分。
我制作了一个 service
插件来代表 你的 API。 upstream
插件代表您要代理的实际上游 API。
所有请求都通过 service
并代理到 upstream
,它只打印出所有 headers 和收到的有效负载。
如果原始请求不包含带有 sessionId 的 cookie,则会在 upstream
上命中一条路由以获取一个。然后,当响应返回下游时,使用此值设置 cookie。
代码在这里:https://github.com/mtharrison/hapijs-proxy-trouble
使用 curl 和您的浏览器试试看。
获取: 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'
};
TL;DR
我如何拦截一个请求,ping 一个 ID 的不同路由,将 ID 存储在 session,然后使用我的 ID 继续原始请求(尤其是 PUT/POST 和负载)刚拿到?
背景
我正在使用 HapiJS (8) 将来自客户端的请求代理到现有的 API(我无法控制 processes/logic)。 API 要求每个请求在查询字符串或负载中包含一个 'session ID'(取决于 http 方法)。为了获得一个 session ID,我所要做的就是索要一个……不需要 username/pwd(它在 headers 中使用基本身份验证)。如果不更新,session ID 每 24 小时过期一次。每个客户都有自己的 session ID。
我目前正在使用 hapi-auth-cookie 来存储 session ID 的值,在需要 ID 时会查询该值。如果 ID 已过期或为空,我需要在客户端请求成功代理到 API.
之前请求一个新 ID当前解决方案
当客户端的请求方法是 'GET' 时,我正在利用 hapi-auth-cookie docs. The request is intercepted by hapi-auth-cookie, if a new session ID is needed, a request is sent to that particular route to get it, the API returns the ID which is then assigned to the Hapi session, and then (using Wreck) a reply.redirect
[=66 中描述的 appendNext
配置非常优雅地处理这个挑战=] 到它完成的原始 GET 请求。无缝优雅。
但是,我不知道如何使用包含数据有效负载的不同 http 方法来完成相同的流程。
除了 reply.redirect
之外,还有什么东西可以在保持原始有效负载和方法的同时实现相同的目标吗?或者一般来说有更好的方法来做到这一点吗?
代码(目前适用于 'GET' 个请求)
主应用程序文件(hapi-auth-cookie 配置)
# 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'
利用 session 身份验证并调用 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}
获取新 session ID 的路径
module.exports = [
{
path: '/api/SessionID'
method: 'GET'
config: SessionController.session
}
]
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
最终解决方案
根据 Matt Harrison 的回答,我创建了一个自定义插件,该插件被注册为身份验证方案,因此我可以按路由控制它。
插件代码如下:
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
虽然不完全忠实于您的代码,但我已经整理了一个示例,其中包含我认为您正在使用的所有部分。
我制作了一个 service
插件来代表 你的 API。 upstream
插件代表您要代理的实际上游 API。
所有请求都通过 service
并代理到 upstream
,它只打印出所有 headers 和收到的有效负载。
如果原始请求不包含带有 sessionId 的 cookie,则会在 upstream
上命中一条路由以获取一个。然后,当响应返回下游时,使用此值设置 cookie。
代码在这里:https://github.com/mtharrison/hapijs-proxy-trouble
使用 curl 和您的浏览器试试看。
获取: 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'
};