HapiJS代理麻烦

时间:2015-03-09 18:47:57

标签: node.js session cookies proxy hapijs

TL; DR

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

背景

我正在使用HapiJS(8)将客户端的请求代理到现有的API(我无法控制进程/逻辑)。 API要求每个请求都包含一个'会话ID'在查询字符串或有效负载中(取决于http方法)。为了获得会话ID,我所要做的只是要求一个...没有用户名/密码需要(它在标题中使用Basic auth)。会话ID如果没有续订,则每24小时到期一次。每个客户都有自己的会话ID。

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

当前解决方案

当客户的请求方法是“GET'”时,我正在使用hapi-auth-cookie docs中描述的appendNext配置非常优雅地处理此挑战。该请求被hapi-auth-cookie截获,如果需要新的会话ID,则向该特定路由发送请求以获取它,API返回ID,然后将其分配给Hapi会话,然后(使用{ {3}})reply.redirect返回完成的原始GET请求。无缝而优雅。

但是,我无法弄清楚如何使用包含数据有效负载的不同http方法来完成相同的流程。

除了reply.redirect之外还有什么能够在保持原有的有效载荷和方法的同时实现相同的目标吗?或者有更好的方法来做到这一点吗?

代码(目前适用于' GET'请求)

主应用程序文件(hapi-auth-cookie configs)

# 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'

利用会话身份验证并调用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

最终解决方案

根据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

1 个答案:

答案 0 :(得分:1)

虽然不完全忠实于您的代码,但我已经汇总了一个包含我认为您正在使用的所有部分的示例。

我已经制作了一个service插件来代表您的 API。 upstream插件代表您要代理的实际上游API。

所有请求都通过service并被代理到upstream,它只打印出收到的所有标头和有效负载。

如果原始请求不包含带有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'    
};