【问题标题】:HapiJS Proxy TroubleHapiJS 代理问题
【发布时间】:2015-03-14 04:30:45
【问题描述】:

TL;DR

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

背景

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

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

当前解决方案

当客户端的请求方法是“GET”时,我正在使用hapi-auth-cookie docs 中描述的appendNext 配置非常优雅地处理这个挑战。请求被 hapi-auth-cookie 拦截,如果需要新的会话 ID,则向该特定路由发送请求以获取它,API 返回 ID,然后分配给 Hapi 会话,然后(使用 @ 987654323@) a reply.redirect 返回到它完成的原始 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'

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

【问题讨论】:

  • 请填写邮政编码。这听起来像是您可以在onPreHandleronRequest(取决于您当时需要的信息)服务器扩展点中轻松处理的事情。此时,您可以在点击处理程序之前更改请求。查看请求生命周期:hapijs.com/api#request-lifecycle
  • 或者您可以在mapUri 选项中将reply.proxy 与您的ID/端点逻辑一起使用。不过,一些代码将有助于决定这一点。
  • @MattHarrison 添加了当前适用于“GET”请求的代码示例。我知道请求生命周期(让我从 Express 切换的重要因素之一),但我不确定如何利用它来解决这个问题。谢谢!

标签: node.js session cookies proxy hapijs


【解决方案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'    
};

【讨论】:

  • 马特这看起来很棒。我整天都在开会,但我会尽快尝试并报告。
  • @Nick 好的,希望对您有所帮助
  • 绝对让我走上了正确的道路。如果您好奇,我将我的最终版本添加到上面的原始问题中。再次感谢!
猜你喜欢
  • 1970-01-01
  • 2018-08-26
  • 2015-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-28
  • 2013-12-02
  • 1970-01-01
相关资源
最近更新 更多