- 投稿日:2020-04-02T21:47:58+09:00
Netlify Functions で API の URL を rewrite する
はじめに
Netlify Functions で作成する API の URL 形式を 初期設定の
/.netlify/functions/example?hoge=10
のような形から/example/10
形式に rewrite する方法です。netlify.toml の指定
Netlify ではプロジェクトのルートに配置する
netlify.toml
で redirect を指定することができます。また、status
を200
に設定すると redirect ではなく rewrite として機能します。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.path
をpath-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 = 200rewrite が使えないローカル netlify-lambda 環境の考慮も入れた版を github においてあります。
動作サンプル
/v1/digit/幅px/桁数/ナンバー/
として動作します。https://sample-counter.netlify.com/v1/digit/640/9/123456789/
関連
Netlify Functions で古のアクセスカウンター(アクセサリー)をつくる - Qiita
github リポジトリー
- 投稿日:2020-04-02T01:14:21+09:00
イテレータ・ジェネレータで構文解析
はじめに
@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>+パターン指定例
ソースコード
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