取りあえず何か創る

ソフトウェアエンジニアだけど仕事以外でも何か作りたくなったので、主にその記録のためにブログを開設しました。

(AWS CDK)LambdaのLayerでコードを再利用する

0. この投稿の概要

  • AWS Lambdaの異なるFunction間でコードを再利用をするための仕組みであるLayerをAWS CDKでdeployします。
  • 元となるLambda Functionは以前の投稿で実装した図書館アラートシステムです。aburaku.hatenablog.com

1. Layerとは?

  • AWS Lambdaはコードをアップロードすることで、サーバの細かい設定をせずに周期実行やトリガー実行できるアプリを簡単に作成できるサービスです。
  • Lambdaの実行単位であるFunctionは基本的に独立しており、また外部ライブラリを使う場合はそれを丸ごとアップロードする必要があります。
  • Layerは異なるFunction間でコードを再利用する仕組みです。特に外部ライブラリは重くなりがちですが頻繁に更新しないので、自分のコードと切り離すことでdeployの時間を短縮することもできます。
  • 以下のgifはAWSのブログからの引用ですが理解しやすいかと思います。

2. CDKによるdeploy

  • コードはgithubに上げています。

github.com

layer = _lambda.LayerVersion(
    self,
    "Packages",
    code=_lambda.Code.from_asset(LAYER_PATH),
    description="requests module",
)

library_alert_lambda = _lambda.Function(
    self,
    "LibraryAlert",
    code=_lambda.Code.from_asset(API_PATH),
    handler="library_alert.lambda_handler",
    memory_size=512,
    timeout=core.Duration.seconds(120),
    role=lambda_role,
    layers=[layer],   # <- 追加
    **common_params,
)
  • CDKのLayerVesionの必須引数はcodeだけです。pip install requests -t ./packages/pythonのようにtargetオプションをつけてパッケージを指定した場所にインストールをして、その場所をcodeに指定します。
  • この際気をつけるのはpythonというフォルダの下に配置することです。こうしておくとパスが通るのでLambda側で特にパスを追加しなくてもimportが可能になります。

参考:Lambda レイヤーの作成と共有 - AWS Lambda

  • Function側の変更はlayers引数に追加したいlayerをリストで渡してやるだけです。

3. 結果

  • 動作自体は元のコードと変化はしませんが、GUIでLayerを追加したFunctionを見に行くと、確かに追加されていることが確認できます。

既読のリンクを非表示にするchrome拡張機能を作る

0. この投稿の概要

  • マッチングアプリをやっている時に、一度見たプロフィールを一覧から非表示に出来れば何度も同じプロフィールを見ないで済むのではと考えました。
  • 既存のものが見つからなかったので、chrome拡張機能として実装しました。この投稿ではペアーズを対象にして実装例を紹介しています。(顔写真を隠すのが面倒なので、実行例は載せていません…)

1. 機能の概要

自分が実装した拡張機能は以下のような構成になっています。今回初めて拡張機能を作ったので、私の構成は最適ではないと思いますが参考まで。

  • google chrome拡張機能アイコンをクリックすることで機能が動きます。
  • まずbackground.jsがクリックイベントに反応して実行され、chromeの閲覧履歴から対象のURLを取得してlocal storageに保存、その後content.jsを実行します。*1
  • content.jsでアクティブなタブのhtmlから特定のclassを持つリンクを抽出し、それが閲覧履歴に含まれる場合はdisplayプロパティをnoneにすることで非表示にします。

2. 実装例

フォルダ構成

  • 以下の通りになっています。必要なコードは全部載せているので、コピーをして同様の構成を作り、chromeで読み込めば機能を使えます。読み込み方についてはchrome拡張機能のチュートリアルを参考にしてください。
  • イコン画像は何でもいいですが、私は同ページからダウンロードした画像をそのまま使っています。

.
├── background.js
├── content.js
├── images
│ ├── get_started128.png
│ ├── get_started16.png
│ ├── get_started32.png
│ └── get_started48.png
└── manifest.json

manifest.json

  • 拡張機能の設定ファイルみたいなもので、必須のファイルです。拡張機能の名前や呼び出すスクリプト、アイコン画像などを設定します。
  • 特に重要なのはpermissionです。ここで拡張機能がアクセスできる資源を指定しています。今回は閲覧履歴を取得するためのhistory、閲覧履歴を保存するためのstorage、webページを操作するためのactiveTab、スクリプトをページに追加するためのscriptingを許可しています。
{
  "name": "Hide read link",
  "description": "Hide links from web page if you have already read them.",
  "version": "1.0",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  },
  "permissions": ["storage", "activeTab", "scripting", "history"],
  "action": {
    "default_title": "Click",
    "default_icon": {
      "16": "/images/get_started16.png",
      "32": "/images/get_started32.png",
      "48": "/images/get_started48.png",
      "128": "/images/get_started128.png"
    }
  },
  "icons": {
    "16": "/images/get_started16.png",
    "32": "/images/get_started32.png",
    "48": "/images/get_started48.png",
    "128": "/images/get_started128.png"
  }
}

background.js

let microseconds3Days = 1000 * 60 * 60 * 24 * 3;
var threeDaysAgo = (new Date).getTime() - microseconds3Days;
// Execute when the icon is clicked.
chrome.action.onClicked.addListener((tab) => {
  storeHistory(tab);
});

function storeHistory(tab) {
  chrome.history.search({
    "text": "https://pairs.lv/user/profile/",
    "startTime": threeDaysAgo,
    "maxResults": 10000,
  },
  function(historyItems) {
    let historyList = [];
    for (let i = 0; i < historyItems.length; ++i) {
      let url = historyItems[i].url;
      let url_split = url.split("/");
      // Store only profile ID
      historyList.push(url_split[url_split.length-1]);
    };
    chrome.storage.local.set({ historyList });
    chrome.scripting.executeScript({
      target: {tabId: tab.id},
      files: ['content.js'],
    });
  });
};
  • 拡張機能のアイコンをクリックするとchrome.action.onClicked.addListenerで指定した関数が実行されます。
  • 今回は3日分のペアーズのプロフィール閲覧履歴をchrome.history.searchで取得しています。
  • URLは表示される場所によって変わるようなので、プロフィール固有と思われるIDをURLから抽出しています。そのリストをchrome.storage.local.setで保管しています。
  • 最後にchrome.scripting.executeScriptでcontent.jsをwebページに埋め込みます。

content.js

  • chrome.storage.local.getで保存しておいた履歴データを読み込む意外は普通のjavascriptコードです。対象リンクのクラスはchromeデベロッパーツールで確認しました。
function deleteReadLinks() {
  chrome.storage.local.get("historyList", ({ historyList }) => {
    let historySet = new Set(historyList)
    let profiles = document.getElementsByClassName("css-opde7s");
    for (let i = 0; i < profiles.length; ++i) {
      if ("href" in profiles[i]) {
        let url_split = profiles[i].href.split("/");
        if (historySet.has(url_split[url_split.length-1])) {
          profiles[i].style.display = "none";
        }
      }
    };
  });
}
deleteReadLinks();

3. 参考

*1:わざわざ別スクリプトを呼んでいるのは、何故かbackground.jsから直接webページを操作できなかったため。

随時更新 : 1人でリラックスできるホテルのまとめ(主に都心)

この投稿の目的

 私はたまにリラックスのために1人で近場のホテルで過ごすことがあります。備忘録も兼ねて良かったと思うホテルについてまとめていきます。同じようにホテルステイをされている方の参考になれば幸いです。
 ちなみに自分が重視するポイントは以下の通りです。

  • 設備: 座り心地の良い椅子、あるいはソファが設置されていること。インターネットの速度が遅すぎないこと。
  • 費用: 1,2ヶ月に1回くらい行きたいので、高くても1泊1万円には抑えたい。
  • 環境: 静かに過ごしたいので、車通りの激しい場所や地下鉄を除く駅を避けたい。

銀座キャピタルホテル萌木

  • 設備: エグゼクティブシングルルームには大きめのソファが設置されていました。

  • 費用: エグゼクティブシングルは税込み6,000円~7,000円が相場のようです。自分も6,000円で宿泊しました。参考: 宿泊プラン一覧(楽天トラベル)
  • 環境: 築地市場の近くにあります。私は休日に泊まりましたが、車の音は特に気になりませんでした。

   

サードニクス東京

  • 設備: サードニクスダブルルームにはかなり大きめのコーナーソファが設置されていました。

  • 費用: サードニクスダブルルームは税込み8,000円以上が相場のようです。自分はキャンペーンを活用して安価に宿泊しましたが、基本的に1万円を少し超えそうです。その分アメニティが豊富に揃っています。参考: 宿泊プラン一覧(楽天トラベル)
  • 環境: 八丁堀駅の近くで、休日は静かな環境でした。

   

図書館APIで新刊の入荷をいち早く知る(AWS CDKで実装)

0. 簡単なまとめ

  • 図書館に自分が読みたい新刊が入荷された時に通知するシステムをAWSに実装。
  • コードは下記のリンク参照。AWSのCDKを使ったのでコードを動かすだけで簡単に同じ環境を準備できるはず。

    github.com

     

1. やりたいこと

  • 図書館に自分が読みたい新刊が入荷されたことをいち早く知り予約することで、待たずに新刊を読めるようになりたい。
  • 定期的に自動で入荷状況を調べる必要がある。金を使わないために図書館を使っているので、当然このシステムを稼働させるのに金を使いたくない。

2. 実現方法

Lambda
Lambda
Amazon API...
Amazon
DynamoDB
Amazon...
Amazon EventBridge
Amazon EventB...
Lambda
Lambda
メール通知本や図書館の編集定期実行図書館API
Viewer does not support full SVG 1.1

3. 仕様と実装

  • コードは下記リンクに置いてます。今回はAWS  CDKなる、コードでAWSのリソースの準備を行えるツールを使って実装しました。いわゆるInfrastructure as Codeというやつですね。言語はPython3です。

    GitHub - tsubasawb/LibraryAlert

  • 主要コンポーネントの役割
    • 図書館API
      • 本のISBNと図書館のIDを渡すと本の貸し出し状況を返す。
    • API Gateway
      • 以下のAPIを用意している。
        機能
        エンドポイント
        HTTPメソッド
        全ステータス取得
        /
        GET
        本を新規追加
        /books
        POST
        図書館を新規追加
        /libraries
        POST
        本の削除
        /books/<isbn>
        DELETE
        図書館の削除
        /libraries/<id>
        DELETE
      • いちいちAPIを叩くのは面倒なので、Vue.3を利用してwebページ経由でAPIを叩くようにした。どうせ自分しか使わないので、index.htmlはローカルに置いてある。
    • EventBridge
      • Lambdaを定期的に実行するトリガーを出力する。今回は1時間に1回実行するよう設定した。
    • Lambda
      • API: APIの数だけLambda Functionを用意している。API Gatewayからのリクエストをトリガーに動作して、DynamoDBの操作を行う。
      • 定期実行: メインの関数。DBに登録されている図書館と本の全ペアの入荷状況を図書館APIで調べ、アップデートがある場合はDBを更新してメールで通知する。
    • DynamoDB
      • 監視対象の図書館、本とそれらの入荷状況を持つ。図書館APIでは貸し出し状況は細かく分類されているが、今回は本が入荷されているかどうかだけを考慮した。
  •  その他のコンポーネント
    • Lambdaがメールアドレスやアプリケーションキーのような機密情報にアクセスする必要がある。コードに埋め込みたくないので、SSMのParameter Storeを利用した。
    • Lambdaの定期実行で異常が発生していないかウォッチするためにCloudWatch Logsを利用。Lambda実行時のエラーを検知して、Amazon SNSで登録しておいたメールアドレスに通知する。
    • デフォルトの設定ではURLを知っていたら誰でもAPIにアクセスできてしまうので、API使用量とAPIキーを使ってアクセスを制限する。

4. 使い方

  • 環境構築
    • AWSの登録など
      • AWSアカウント取得、シークレットキー作成、CDKのインストールが必要です。このページが参考になります。

        コードで学ぶAWS入門

    • googleのアプリパスワード設定
    • 図書館APIアプリケーションキー登録
    • 環境変数の設定
      • cloneしたディレクトリ内に.envファイルを作成し、下記の通りgmailアドレス、googleアプリパスワード、図書館APIアプリケーションキーを設定してください。
      • EMAIL = "your mail address@gmail.com"
        GOOGLE_PASS = "your password"
        APP_KEY = "your application key"
        
    • cdk deployの実行
      • python3 -m pip install -r requirements.txtで必要なモジュールをインストールして、cdk deployを実行するとAWSのサービスが立ち上がります。
      • 環境を削除したい場合はcdk destroyを実行してください。
    • エンドポイントとAPIキーの登録
      • cdk deployが完了後にLibraryAlert.LibraryAlertApiEndpointXXX = https://XXXX.amazonaws.com/prod/という表示がコンソールに現れます。このURLがエンドポイントです。
      • APIキーはAmazon API GatewayGUI上で確認します。作成されたAPI(LibraryAlertApi)の下記の場所でAPIキーを表示できます。

      • gui/config.jsというファイルを作成して、以下のようにエンドポイントとAPIキーを記載します。*1
        const config = {
            endpoint: "your endpoint URL",
            apikey: "your API key",
        }
  • 図書館と本の登録
    • index.htmlをブラウザで開き、まずは図書館の登録をします。図書館のIDはカーリルの図書館マップで利用したい図書館のページから取得します。このIDは市町村内の図書館で共通なようなので、例えば"Tokyo_Machida"を指定すると町田の市立図書館全ての状況をチェックします。

      このIDをページ右上のフォームに書き、"図書館の追加"ボタンを押して更新をすると、下の表に追加した図書館が表示されます。

    • 次に本の登録をします。読みたい本のISBN-10を左上のフォームに書き、"本の追加"ボタンを押して更新すると、下の表に追加した本が表示されます。ISBN-10はamazonで確認できます。*2

    • 以降は登録した図書館と本の組み合わせ全てが表に追加されて監視対象になります。図書館や本を削除したい場合は、図書館IDやISBNをそれぞれのフォームに入れて削除ボタンを押してページを更新してください。
    • 1時間毎に起動するLambda functionで入荷状況が調べられます。もし新しく入荷があった場合は、「新刊入荷情報」というタイトルのメールが届きます。メール内容に図書館の予約ページへのリンクがあります。

5. 参考

*1:これも.envに定義した方がいいんだろうけど、インストールするものが増えるのが嫌なのでjsファイル内に定義した。

*2:ISBNに貼られているリンクはカーリルのページ。貸し出し状況を表示する場合はカーリルへのリンクを貼らなければいけないらしいので。