コンテンツにスキップ
サービスプロキシ

サービスプロキシ

Since v8.5.2

JSHサービスは、独自のHTTPサーバーを起動し、machbase-neoの/web/services/<service_name>/<prefix>/*パスで公開できます。 サービス開発者は、外部クライアントにサービスのポートを知らせる必要はなく、起動時にプロキシエンドポイントを登録します。 クライアントはmachbase-neoと同じオリジンでアクセスするため、別途ポートを調べたりCORSを設定したりする必要はありません。

概要

実行中のJSHサービスが、SERVICE_CONTROLLERを介してサービスプロキシを動的に登録します。 登録したパスへのHTTPリクエストは、登録先のサーバーにリバースプロキシされます。

公開パスの形式は以下のとおりです。

/web/services/<service_name>/<prefix>/*

たとえば、サービス名がgithub.com/acme/chart、prefixが/api/の場合、クライアントは次のアドレスにアクセスします。

/web/services/github.com/acme/chart/api/series

命名規則

service_nameに、別途名前空間の制限は設けていません。 同じservice_nameprefixの組み合わせは、最初に登録したプロセスが所有します。 後から別のtargetで同じ組み合わせを登録すると、競合エラーが発生します。

パッケージとして配布するサービスは、名前の競合を避けるため、パッケージ名をservice_nameに使用することを推奨します。

service: 'github.com/acme/chart'

1つのサービスに、複数のプロキシエンドポイントを登録できます。

service.proxy.register({
    service: 'github.com/acme/chart',
    prefix: '/api/',
    target: 'http://127.0.0.1:18080'
}, callback);

service.proxy.register({
    service: 'github.com/acme/chart',
    prefix: '/assets/',
    target: 'http://127.0.0.1:18081'
}, callback);

Targetの制限

プロキシのtargetは、machbase-neoサーバーが安全にアクセスできるローカルエンドポイントに制限されます。

許可されるtargetは以下のとおりです。

  • http://127.0.0.1:<port>
  • http://localhost:<port>
  • その他のループバックIPアドレス
  • unix://<absolute_socket_path>

外部ホストとhttps://のtargetは、既定では許可しません。 この制限は、サービスプロキシが任意の外部アドレスに接続するオープンプロキシになることを防ぎます。

登録

サービスのコードでは、serviceモジュールのproxy.register()を使用します。

const service = require('service');

service.proxy.register({
    service: 'github.com/acme/chart',
    prefix: '/api/',
    target: 'http://127.0.0.1:18080'
}, function(err, entry) {
    if (err) {
        console.println('proxy register failed:', err.message);
        return;
    }
    console.println('proxy registered:', entry.service, entry.prefix);
});

登録項目には、以下のフィールドを使用します。

フィールド説明
serviceString公開パスのサービス名
prefixStringサービス配下のプロキシのprefix
targetStringリクエストを転送する、ローカルHTTPまたはUnixソケットのエンドポイント
stripPrefixString省略可能。targetに転送する前に除去する公開パスのprefix
healthPathString省略可能。ヘルスチェック用パスのメタデータ

stripPrefixを省略すると、既定で/web/services/<service_name>/<prefix>を除去してtargetに転送します。 たとえば、/web/services/github.com/acme/chart/api/seriesへのリクエストは、targetサーバーに/seriesとして転送します。

stripPrefix

サービス内部のルーターが公開パスの一部を必要とする場合は、stripPrefixを直接指定できます。 たとえば、プロキシのprefixを/api/で登録し、targetサーバーで/api/seriesを処理するには、サービスのベースパスだけを除去します。

service.proxy.register({
    service: 'github.com/acme/chart',
    prefix: '/api/',
    target: 'http://127.0.0.1:18080',
    stripPrefix: '/web/services/github.com/acme/chart'
}, callback);

この設定では、以下のようにパスを転送します。

公開リクエストパスtargetサーバーが受け取るパス
/web/services/github.com/acme/chart/api/series/api/series
/web/services/github.com/acme/chart/api/healthz/api/healthz

stripPrefixを省略すると、既定で/web/services/github.com/acme/chart/apiを除去するため、targetサーバーは/series/healthzを受け取ります。

登録解除

サービスの終了時には、登録したプロキシエンドポイントを解除することを推奨します。

特定のprefixだけを解除するには、以下のように呼び出します。

service.proxy.unregister('github.com/acme/chart', '/api/', function(err) {
    if (err) {
        console.println('proxy unregister failed:', err.message);
    }
});

サービス名に属するすべてのエンドポイントを解除するには、prefixを省略します。

service.proxy.unregister('github.com/acme/chart', function(err) {
    if (err) {
        console.println('proxy unregister failed:', err.message);
    }
});

検索

現在の登録状態は、proxy.list()proxy.get()で確認できます。

service.proxy.list('github.com/acme/chart', function(err, entries) {
    if (err) {
        console.println(err.message);
        return;
    }
    console.println(JSON.stringify(entries));
});

service.proxy.get('github.com/acme/chart', '/api/', function(err, entry) {
    if (err) {
        console.println(err.message);
        return;
    }
    console.println(JSON.stringify(entry));
});

servicectlの管理コマンド

稼働中のプロキシの登録状態は、servicectl proxyコマンドでも確認・管理できます。 このコマンドはSERVICE_CONTROLLERに接続するため、--controllerオプションまたはSERVICE_CONTROLLER環境変数を使用します。

servicectl proxy list
servicectl proxy list github.com/acme/chart
servicectl proxy get github.com/acme/chart /api/

テストや手動運用では、CLIから直接登録することもできます。

servicectl proxy register github.com/acme/chart /api/ http://127.0.0.1:18080 --health-path /healthz
servicectl proxy unregister github.com/acme/chart /api/

proxy unregister <service_name>のようにprefixを省略すると、そのサービス名で登録したすべてのプロキシエンドポイントを削除します。 通常のサービスコードでは、開始時にservice.proxy.register()、終了時にservice.proxy.unregister()を呼び出す方式を推奨します。servicectl proxyは、運用確認とデバッグに使用します。

TCP 例

次の例は、サービスがローカルHTTPサーバーを起動し、/web/services/github.com/acme/chart/api/*パスに登録する手順を示します。

const http = require('http');
const service = require('service');

const serviceName = 'github.com/acme/chart';
const port = 18080;

const server = new http.Server({ network: 'tcp', address: '127.0.0.1:' + port });

server.get('/healthz', function(ctx) {
    ctx.text(http.status.OK, 'ok');
});

server.get('/*path', function(ctx) {
    ctx.json(http.status.OK, { path: ctx.request.path });
});

server.serve(function() {
    service.proxy.register({
        service: serviceName,
        prefix: '/api/',
        target: 'http://127.0.0.1:' + port,
        healthPath: '/healthz'
    }, function(err) {
        if (err) {
            console.println('proxy register failed:', err.message);
            return;
        }
        console.println('proxy ready:', '/web/services/' + serviceName + '/api/');
    });
});

process.on('exit', function() {
    server.close();
    service.proxy.unregister(serviceName, '/api/', function() {});
});

Unixドメインソケットの例

JSHのhttp.Serverは、network: 'unix'設定に対応しています。 サービス専用のHTTPサーバーを、TCPポートではなくUnixドメインソケットにバインドすると、外部ポートを開かずにサービスプロキシと接続できます。 targetには、unix://の後にソケットの絶対パスを指定します。

const http = require('http');
const service = require('service');

const serviceName = 'github.com/acme/chart';
const socketPath = '/tmp/acme-chart.sock';

const server = new http.Server({ network: 'unix', address: socketPath });

server.get('/healthz', function(ctx) {
    ctx.text(http.status.OK, 'ok');
});

server.get('/*path', function(ctx) {
    ctx.json(http.status.OK, { path: ctx.request.path });
});

server.serve(function() {
    service.proxy.register({
        service: serviceName,
        prefix: '/api/',
        target: 'unix://' + socketPath,
        healthPath: '/healthz'
    }, function(err) {
        if (err) {
            console.println('proxy register failed:', err.message);
            server.close();
            return;
        }
        console.println('proxy ready:', '/web/services/' + serviceName + '/api/');
    });
});

process.on('exit', function() {
    server.close();
    service.proxy.unregister(serviceName, '/api/', function() {});
});

たとえば、/web/services/github.com/acme/chart/api/seriesへのリクエストは、Unixソケットで接続したサービスサーバーの/seriesに転送されます。 ソケットのパスは絶対パスである必要があります。本番環境では、サービス間で競合しないパスを使用してください。

注意事項

  • 登録情報はランタイムの状態です。サービスを再起動した場合は、再登録する必要があります。
  • 同じservice_nameprefixの組み合わせは、最初に登録したプロセスが所有します。
  • 未登録の/web/services/<service_name>/<prefix>/*リクエストは、他のパッケージ処理にフォールバックしません。
  • サービスには、外部公開ポートではなく、ループバックまたはUnixドメインソケットの使用を推奨します。
最終更新日