20200402のNode.jsに関する記事は2件です。

Netlify Functions で API の URL を rewrite する

はじめに

Netlify Functions で作成する API の URL 形式を 初期設定の /.netlify/functions/example?hoge=10 のような形から /example/10 形式に rewrite する方法です。

netlify.toml の指定

Netlify ではプロジェクトのルートに配置する netlify.toml で redirect を指定することができます。また、status200 に設定すると redirect ではなく rewrite として機能します。

Rewrites and proxies

When you assign an HTTP status code of 200 to a redirect rule, it becomes a rewrite. This means that the URL in the visitor's address bar remains the same, while Netlify's servers fetch the new location behind the scenes.

URL の指定にはプレースホルダー が使えるので、例えば /v1/digit/200/6/123456/ のような URL を ?width=600&digit=6&number=123456 に変換する記述は次のようにかけそうです。

[[redirects]]
    from = "/v1/digit/:width/:digit/:number/"
    to = "/.netlify/functions/digit/?width=:width&digit=:digit&number=:number"
    status = 200

が、残念ながらリライトはされるものの、(2020/4 現在)Lambda エンドポイントの event.queryStringParameters に値が渡ってきません。

exports.handler = async (event) => {
    console.log(event.queryStringParameters); // {}
}

Netlify Community にも同件の報告があがっていました。

queryStringParameters not working with redirect on production - Support - Netlify Community

event.path から取得する

Community のほうに event.path から取得してみてはどうかとありましたので、(悲しいですが)次のように event.pathpath-parser で URL をパースすることにしました。

import { Path } from 'path-parser';

function parseQueryArgs(rewriteUrl, urlpath, format) {
    const parse = Path.createPath(rewriteUrl).test(urlpath);
    let result = {};
    for(const key in format) {
        if(parse && parse[key]) {
            result[key] = parse[key];
        } else {
            // default value
            result[key] = format[key]
        }
    }

    return result;
}

exports.handler = async (event) => {
    const args = parseQueryArgs(
        '/v1/digit/:width<\\d+>/:digit<\\d+>/:number<\\d+>/',
        event.path,
        {
            width : 160,
            digit: 6,
            number: 12345
        }
    );
    // ..snip
};

この場合の netlify.toml は単純に次のようになります。

[[redirects]]
    from = "/v1/digit/*"
    to = "/.netlify/functions/digit"
    status = 200

rewrite が使えないローカル netlify-lambda 環境の考慮も入れた版を github においてあります。

動作サンプル

Screenshot_2020-04-02 fujilcd.png

/v1/digit/幅px/桁数/ナンバー/ として動作します。

https://sample-counter.netlify.com/v1/digit/640/9/123456789/

関連

Netlify Functions で古のアクセスカウンター(アクセサリー)をつくる - Qiita

github リポジトリー

h1romas4/netlify-functions-template

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

イテレータ・ジェネレータで構文解析

はじめに

@ikiuoさんが投稿された「パターン指定型パスワード ジェネレーター (JS版) - Qiita」を拝見したところ、イテレータとジェネレータでコンパクトに作れそうな気がしたので作ってみました。
構文パーサとして使えるように、「途中で戻したり進めたりできるイテレータ」も作りました。

パターン構文

パターン構文のBNFはこんな感じでしょうか。

<syntax> ::= <item> | <item> <syntax>
<item> ::= <char> | "%" <percent>
<percent> ::= [<repeat>] ( <pattern> | "{" <brace> "}" | "[" <blacket> "]" | <char> )
<repeat> ::= ("0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9")+
<pattern> ::= <special> | <char>
<special> ::= "b"|"o"|"d"|"X"|"x"|"A"|"C"|"c"|"B"|"V"|"v"|"D"|"Q"|"q"|"Y"|"Z"|"z"|"W"|"L"|"l"
<brace> ::= <percent>+
<blacket> ::= <char>+

パターン指定例

image.png

ソースコード

percent, brace, blacket をイテレータにし、イテレータが内容文字列を次々ジェネレートしてくれれば、ジェネレートされた文字列から乱数で1文字選んで次々文字列結合してパスワード文字列の完成、という目論みです。
ジェネレータはデータを読み出すと無くなってしまうので、イテレータが呼び出される度に文字列をジェネレートするジェネレータを作って返しています。

function iter(pattern) {
    let index = 0
    return {
        [Symbol.iterator]: function() {
            return this
        },
        next: function() {
            if (index < pattern.length) {
                return { value: pattern[index++], done: false }
            } else {
                return { done: true }
            }
        },
        fetch: function() {
            return pattern[index]
        },
        skip: function() {
            index++
        },
    }
}

function charset(pattern, exclude = '') {
    function* chars() {
        let start = null
        const p = iter(pattern)
        for (let c of p) {
            if (c === '-' && start && p.fetch()) {
                const { value } = p.next()
                const end = value.charCodeAt(0)
                for (let code = start + 1; code <= end; code++) {
                    yield String.fromCharCode(code)
                }
                start = null
            } else {
                if (c === '\\' && p.fetch()) {
                    ( { value: c } = p.next() )
                }
                yield c
                start = c.charCodeAt(0)
            }
        }
    }
    return [...chars()].filter(c => !exclude.includes(c)).join('')
}

function blacket(parser) {
    let chars = ''
    let in_escape = false
    let c = parser.next().value
    while (in_escape || c !== ']') {
        chars += c
        in_escape = !in_escape && c === '\\'
        c = parser.next().value
    }
    chars = charset(chars)
    return {
        [Symbol.iterator]: function* () {
            yield chars
        }
    }
}

function brace(parser) {
    const iterators = []
    while (parser.fetch() !== '}') {
        iterators.push(percent(parser))
    }
    parser.skip()
    return {
        [Symbol.iterator]: function* () {
            for (let iterator of iterators) {
                yield* iterator
            }
        }
    }
}

const SPECIAL = {
    b: '01',
    o: charset('0-7'),
    d: charset('0-9'),
    X: charset('0-9A-F'),
    x: charset('0-9a-f'),
    A: charset('A-Za-z'),
    C: charset('A-Z'),
    c: charset('a-z'),
    B: 'AEIOUaeiou',
    V: 'AEIOU',
    v: 'aeiou',
    D: charset('A-Za-z', 'AEIOUaeiou'),
    Q: charset('A-Z', 'AEIOU'),
    q: charset('a-z', 'aeiou'),
    Y: charset('0-9A-Za-z'),
    Z: charset('0-9A-Z'),
    z: charset('0-9a-z'),
    W: charset('0-9A-Za-z_'),
    L: charset('0-9A-Z_'),
    l: charset('0-9a-z_'),
}

const SUBMODE = {
    '[': blacket,
    '{': brace,
}

function percent(parser) {
    let repeat = 0
    let c = parser.next().value
    while ('0' <= c && c <= '9') {
        repeat = repeat * 10 + parseInt(c)
        c = parser.next().value
    }
    repeat = repeat || 1
    let iterator
    if (SUBMODE[c]) {
        iterator = SUBMODE[c](parser)
    } else if (SPECIAL[c]) {
        iterator = [SPECIAL[c]]
    } else {
        iterator = [c === '\\' && parser.next().value || c]
    }
    return {
        [Symbol.iterator]: function* () {
            for (let i = 0; i < repeat; i++) {
                yield* iterator
            }
        }
    }
}

function* parse(pattern) {
    let parser = iter(pattern)
    for (let c of parser) {
        if (c === '%') {
            yield* percent(parser)
        } else {
            yield c === '\\' && parser.next().value || c
        }
    }
}

function password(pattern) {
    return [...parse(pattern)].map(s => s[Math.random()*s.length|0]).join('')
}

console.log(password('qiita-%d%X%x%x%A%C%c%c%B%V%v%D%Q%q%Y%Z%z%W%L%l'))
console.log(password('qiita-%3d'))
console.log(password('qiita-%{dXxxACccBVvDQqYZzWLl}'))
console.log(password('%8[01]'))
console.log(password('%8[S-Z]'))
console.log(password('%8[S\\-Z]'))
console.log(password('qiita%4{-c3d}'))
console.log(password('qiita%4{-Qvqd}'))
console.log(password('qiita%2{-C3{2c[13579]}}'))

実行結果

qiita-6C20VYymeUadTsKP4wTh
qiita-465
qiita-3Ce0PMeeEAeXRjqZqEX0
10101101
VWWZTTUY
ZS-ZSS-S
qiita-o350-z239-g090-k848
qiita-Lup7-Pep9-Wif2-Jix2
qiita-Fnc9yp7zn7-Uru5ia9fe7
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む