先日、upstash(特にredis)を使ってみました。 今回は、oak のセッションにupstashを使ってみます。 ドキュメントに書いていない(と思われる)落とし穴がありました。
参考
oak_session を upstash で使う(失敗) まずは安直に、upstash_redis を oak_session の Redis store に登録。
server.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import "https://deno.land/std@0.150.0/dotenv/load.ts" ;import { Application , Context , Router } from "https://deno.land/x/oak/mod.ts" ;import { RedisStore , Session } from "https://deno.land/x/oak_sessions/mod.ts" ;import { Redis } from "https://deno.land/x/upstash_redis/mod.ts" ;const redis = new Redis ({ url : Deno .env .get ("UPSTASH_URL" )!, token : Deno .env .get ("UPSTASH_TOKEN" )!, }); const router = new Router ();router.get ("/" , async (context : Context ) => { const name = await context.state .session .get ("name" ); context.response .body = ` <!DOCTYPE html> <html> <body> <div> ${!name ? "" : "name=" + name} </div> <form method="POST"> <input name="name"> <button type="submit">submit</button> </form> </body> </html> ` ;}); router.post ("/" , async (context : Context ) => { const form = await context.request .body ({ type : "form" }).value ; const name = form.get ("name" ); if (!!name) context.state .session .set ("name" , name); context.response .redirect ("/" ); }); type AppState = { session : Session ; }; const app = new Application <AppState >();const store = new RedisStore (redis);app.use (Session .initMiddleware (store)); app.use (router.routes ()); app.use (router.allowedMethods ()); await app.listen ({ port : 8080 });
動作させてアクセスしてみると次のエラーになります。
1 2 3 $ deno run -A server.ts [uncaught application error]: SyntaxError - "[object Object]" is not valid JSON
Redis と同じ動きをしていないようです。 ここで確認してみると、redis モジュールと uppstash_redis モジュールの間で、次のように動作の差異がありました。
redisモジュール 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $ deno > import { connect } from 'https://deno.land/x/redis@v0.25.0/mod.ts' const redis = await connect({ hostname: 'redis' , port: 6379 }) undefined > await redis.set("k1" ,{a:1,b:2}) "OK" > await redis.get("k1" ) "[object Object]" > await redis.set("k1" ,'{"a":1,"b":2}' ) "OK" > await redis.get("k1" ) '{"a":1,"b":2}' > JSON.parse(await redis.get("k1" )) { a: 1, b: 2 }
Redis モジュールはあくまで文字列のやり取りがされており、jsonのパースなどは別途請け負う必要があります。
upstash_redisモジュール 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ deno > import "https://deno.land/std@0.150.0/dotenv/load.ts" ; Module {} > import { Redis } from "https://deno.land/x/upstash_redis/mod.ts" ; const redis: any = new Redis({ url: Deno.env.get("UPSTASH_URL" )!, token: Deno.env.get("UPSTASH_TOKEN" )!, }); undefined > await redis.set("k1" ,{a:1,b:2}) "OK" > await redis.get("k1" ) { a: 1, b: 2 } > await redis.set("k1" ,'{"a":1,"b":2}' ) "OK" > await redis.get("k1" ) { a: 1, b: 2 } > JSON.parse(await redis.get("k1" )) Uncaught SyntaxError: "[object Object]" is not valid JSON at JSON.parse (<anonymous>) at <anonymous>:2:6
upstash_redis モジュールは、オブジェクトがパースされてしまっています。 READMEなど見ても具体的な記述は有りませんでしたが、ソースを見ると、automaticDeserialization
というオプションが存在していましたです。 これを設定します。
oak_session を upstash で使う(成功) automaticDeserialization
を設定したのが、次のものです。
server.ts(改修) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import "https://deno.land/std@0.150.0/dotenv/load.ts" ;import { Application , Context , Router } from "https://deno.land/x/oak/mod.ts" ;import { RedisStore , Session } from "https://deno.land/x/oak_sessions/mod.ts" ;import { Redis } from "https://deno.land/x/upstash_redis/mod.ts" ;const redis = new Redis ({ url : Deno .env .get ("UPSTASH_URL" )!, token : Deno .env .get ("UPSTASH_TOKEN" )!, automaticDeserialization : false , }); const router = new Router ();router.get ("/" , async (context : Context ) => { const name = await context.state .session .get ("name" ); context.response .body = ` <!DOCTYPE html> <html> <body> <div> ${!name ? "" : "name=" + name} </div> <form method="POST"> <input name="name"> <button type="submit">submit</button> </form> </body> </html> ` ;}); router.post ("/" , async (context : Context ) => { const form = await context.request .body ({ type : "form" }).value ; const name = form.get ("name" ); if (!!name) context.state .session .set ("name" , name); context.response .redirect ("/" ); }); type AppState = { session : Session ; }; const app = new Application <AppState >();const store = new RedisStore (redis);app.use (Session .initMiddleware (store)); app.use (router.routes ()); app.use (router.allowedMethods ()); await app.listen ({ port : 8080 });
これで動作を確認できます。 登録した名前がずっと表示されるというだけですけれども
oak_session を upstash で使う(型チェックエラー回避) 型チェックを行うと次のようにエラーになります。
1 2 3 4 5 6 7 $ deno check server.ts Check file:///usr/src/app/server.ts error: TS2345 [ERROR]: Argument of type 'import("https://deno.land/x/upstash_redis@v1.12.0-next.1/mod.ts").Redis' is not assignable to parameter of type 'import("https://deno.land/x/redis@v0.25.0/redis.ts").Redis' . Type 'Redis' is missing the following properties from type 'Redis' : isClosed, isConnected, sendCommand, close, and 153 more. const store = new RedisStore(redis); ~~~~~ at file:///usr/src/app/server.ts:44:30
oak_sessionが参照している Redis モジュール本来が本来持っている isClosed
や isConnected
を upstash_redis が持っていないのが原因となります。
敢えてやるかという問題でも有るでしょうが、アサーションで解決させました。
server.ts(型チェックエラー回避) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import "https://deno.land/std@0.150.0/dotenv/load.ts" ;import { Application , Context , Router } from "https://deno.land/x/oak/mod.ts" ;import { RedisStore , Session } from "https://deno.land/x/oak_sessions/mod.ts" ;import { Redis } from "https://deno.land/x/upstash_redis/mod.ts" ;import * as orgRedis from "https://deno.land/x/redis@v0.25.0/mod.ts" ;const redis = new Redis ({ url : Deno .env .get ("UPSTASH_URL" )!, token : Deno .env .get ("UPSTASH_TOKEN" )!, automaticDeserialization : false , }) as any as orgRedis.Redis ; const router = new Router ();router.get ("/" , async (context : Context ) => { const name = await context.state .session .get ("name" ); context.response .body = ` <!DOCTYPE html> <html> <body> <div> ${!name ? "" : "name=" + name} </div> <form method="POST"> <input name="name"> <button type="submit">submit</button> </form> </body> </html> ` ;}); router.post ("/" , async (context : Context ) => { const form = await context.request .body ({ type : "form" }).value ; const name = form.get ("name" ); if (!!name) context.state .session .set ("name" , name); context.response .redirect ("/" ); }); type AppState = { session : Session ; }; const app = new Application <AppState >();const store = new RedisStore (redis);app.use (Session .initMiddleware (store)); app.use (router.routes ()); app.use (router.allowedMethods ()); await app.listen ({ port : 8080 });
これで、型チェックも通ります。
少々強引だが、upstash redis で、ork_session が使用できるのを確認できた。 こうなると、deno deploy を使用する必須サービスのように感じるところもある。 upstash redis は、redis と銘打つものの中身を見るとしっかり fetch が使われているを確認できる。 全く完全にredisではないというのは、覚えておく事項だと感じるところ。
ではでは。