プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

pre タグを使った場合のリンク不具合について

はてなブログで記事を書いているときに気づいたのですが、preタグを使った場合、preタグの後ろのリンクが正しく生成されない場合があります。 (2018/8/24時点の話です。いつからこうなっていたかは定かではありません。あと、私の環境での話です。他の方の環境は知らないです。)

たとえば、

<pre>
test
</pre>

のようにpreタグをいれると

test

このpreタグの後ろではURLを入れても、fotolifeの画像をfotolife記法で書いても

https://blog.mori-soft.com

[f:id:junichim:20180824163634j:plain]

のように正しくリンクが解決されません(fotolife記法がそのまま表示されていると思います)。

ここで、 pre タグに data-unlink="" 属性をつけていれると

<pre data-unlink="">
test2
</pre>
test2

https://blog.mori-soft.com

f:id:junichim:20180824163634j:plain

のように正しく表示されます。

もともと SyntaxHighlighter を使っていたりするので、それが原因かもと思い、SyntaxHighlighter の jsファイルやCSSの読み込みを外しても現象は変わりませんでした。 あ、編集は Markdown モードでやってます。

もし、同じような現象が出てる方がいれば、何かの参考になればと思います。 ではでは。

Syntaxhighlighter V4 に YAML ブラシを追加する

こちらの記事を書いたとき、 Syntaxhighlighter V4 に YAML のブラシがないことに気づきました。 なので、YAMLのブラシを追加できないか調べてみた顛末をまとめておきます。

なお、はてなブログで Syntaxhighlighter v4 を使う話は、以前下記にまとめてますので、ご参考までに。

blog.mori-soft.com

YAML ブラシ

SyntaxHgihlighter に Brushes and Themes というページがあり、そこに公式・非公式のブラシ一覧が載ってます。 ここを見ると、非公式としてですが YAML のブラシがあるようです。

GitHub - ErikWegner/brush-yaml

ということで、まずは、これを使ってみようと思います。

YAML ブラシのビルド

YAML ブラシのページを見ると SyntaxHighlighter のビルドの方法に従って、ビルドしてくれとあります。

Building · syntaxhighlighter/syntaxhighlighter Wiki · GitHub

なので、以前 SyntaxHighlighter V4 をビルドしたのと同じように試してみます。

mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d$ git clone https://github.com/ErikWegner/brush-yaml.git
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d$ cd syntaxhighlighter
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$ ./node_modules/gulp/bin/gulp.js build --brushes=../brush-yaml/brush.js --theme=default
[14:32:21] Failed to load external module @babel/register
[14:32:21] Requiring external module babel-register
[14:32:21] Using gulpfile ~/work/syntaxhighlighter.d/syntaxhighlighter/gulpfile.babel.js
[14:32:21] Starting 'build'...
[14:32:21] Unknown brush "../brush-yaml/brush.js".
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$

うまくいきません。 ちなみに --build=all の場合は問題なくビルドできます。

指定方法を変えてみます。絶対パス

mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$ ./node_modules/gulp/bin/gulp.js build --brushes=/home/mor/work/syntaxhighlighter.d/brush-yaml/brush.js --theme=default
[14:34:07] Failed to load external module @babel/register
[14:34:07] Requiring external module babel-register
[14:34:08] Using gulpfile ~/work/syntaxhighlighter.d/syntaxhighlighter/gulpfile.babel.js
[14:34:08] Starting 'build'...
[14:34:08] Unknown brush "/home/mor/work/syntaxhighlighter.d/brush-yaml/brush.js".
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$

うまくいきません。 公式のブラシは repos ディレクトリ以下にあるようなので、リンクを貼って

mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$ cd repos/
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter/repos$ ln -s ../../brush-yaml .
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter/repos$ cd ..
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$ ./node_modules/gulp/bin/gulp.js build --brushes=repos/brush-yaml/brush.js --theme=default
[14:36:18] Failed to load external module @babel/register
[14:36:18] Requiring external module babel-register
[14:36:19] Using gulpfile ~/work/syntaxhighlighter.d/syntaxhighlighter/gulpfile.babel.js
[14:36:19] Starting 'build'...
[14:36:19] Unknown brush "repos/brush-yaml/brush.js".
mor@DESKTOP-RLA4CF1:~/work/syntaxhighlighter.d/syntaxhighlighter$

やっぱりうまくいきません。

docker で syntaxhighlighter V4 をコンパイル

困ったなと思っていたら、ブラシの生成一式を docker にまとめてくれている方がいらっしゃいました。

github.com

以前の記事を書いた際に参考にした issues に docker のことを追記してくれてました。)

Building: loadReposFromCache(...).error is not a function · Issue #428 · syntaxhighlighter/syntaxhighlighter · GitHub

なので、これを試してみます。

[~/tmp] # docker pull crazymax/syntaxhighlighter
[~/tmp] # mkdir syntax
[~/tmp] # docker run -it --rm -v "$(pwd)/syntax:/syntaxhighlighter/dist" crazymax/syntaxhighlighter:latest

~/tmp/syntax というのがローカル側のディレクトリで、コンテナの /syntaxhighlighter/dist にマウントしています。

で、実行させると、

[~/tmp] # ls -l syntax/
total 880
-rw-r--r-- 1 admin administrators  94034 2018-08-24 12:10 index.html
-rw-r--r-- 1 admin administrators 285507 2018-08-24 12:10 syntaxhighlighter.js
-rw-r--r-- 1 admin administrators 407571 2018-08-24 12:10 syntaxhighlighter.js.map
-rw-r--r-- 1 admin administrators   9094 2018-08-24 12:09 theme-default.css
-rw-r--r-- 1 admin administrators   9264 2018-08-24 12:09 theme-django.css
-rw-r--r-- 1 admin administrators   9474 2018-08-24 12:09 theme-eclipse.css
-rw-r--r-- 1 admin administrators   9120 2018-08-24 12:09 theme-emacs.css
-rw-r--r-- 1 admin administrators   9197 2018-08-24 12:09 theme-fadetogrey.css
-rw-r--r-- 1 admin administrators   9118 2018-08-24 12:10 theme-mdultra.css
-rw-r--r-- 1 admin administrators   9141 2018-08-24 12:10 theme-midnight.css
-rw-r--r-- 1 admin administrators   9135 2018-08-24 12:10 theme-rdark.css
-rw-r--r-- 1 admin administrators  10151 2018-08-24 12:10 theme-swift.css
[~/tmp] # 

のように、syntaxhighlighter のファイルが生成されています。docker実行中の画面を見ると、yamlのブラシも指定されているのが分かります。

[03:10:40] Starting 'build'...
[03:10:50] Brushes: applescript, as3, base, bash, coldfusion, cpp, csharp, css, delphi, diff, erlang, groovy, haxe, java, javafx, javascript, perl, php, plain, powershell, python, ruby, sass, scala, sql, swift, tap, typescript, vb, xml, repos/brush-halcon/brush.js, repos/brush-IEC61131/brush.js, repos/brush-kotlin/brush.js, repos/brush-latex/brush.js, repos/brush-Makefile/brush.js, repos/brush-mel/brush.js, repos/brush-objective-c/brush.js, repos/brush-yaml/brush.js
[03:10:50] Hash: 6ed82d7c6ef7b999f01e

なんで自分で試したときはうまくいかなかったのだろうか?

はてなブログに反映

作成した js ファイルを差し替えます。 はてな側では syntaxhighlighter の指定で

<pre class="brush: yaml">

と書くと、

f:id:junichim:20180824145706p:plain

のように無事に、YAMLもハイライト表示されるようになりました。

Swagger による API Gateway の定義

API Gateway を使う際、だんだんAPIの数が増えてくる(リソース・メソッドが増えてくる)と、コンソールで定義するのが大変になってきます。 特に、APIのリネームなんてことが起こったら、嫌になってきます。

なので、それを避けるために、API定義を Swagger で記述して、それを API Gateway にインポートさせて API 定義を簡単に行えるようにしています。

※ Swagger とは、APIを記述するための仕様で、YAMLやJSONでAPIを記述することができます。アプリケーションのドキュメントからAPIを定義したり、定義したAPIからコードを生成したり、反対に実装から、API定義を起こすこともできるそうです。今回はドキュメント(とはいっても自分でざっくり考えたもの)からAPI定義を記述するための Swagger Editor を使いました。 Swagger そのものは解説記事がたくさんあるので、そちらをご覧ください。これとか分かり易かったかな?

qiita.com

しかし、 AWS 特有の設定等は、標準の Swagger では記述できないため、このままではいちいち手作業で設定する必要が残ります。 そこで、API Gateway の Swagger 拡張を使って、AWS特有の設定も Swagger ファイルで定義してしまおう、という作業を行いました。

ということで、毎度の作業メモです。以下では、記述したい AWS の機能ごとにまとめてあります。

Cognito ユーザープールによるオーソライザーの設定方法

下記記事に Cognito ユーザープールによるオーソライザーを使う場合の Swagger 拡張の書き方がずばり載ってます。

API をユーザープールと統合 - Amazon API Gateway

security definitions を定義して、各APIのメソッドの定義内の security で security definitions で定義した名前を参照すればOKです。 YAML だとこんな感じになります。

securityDefinitions:
  jm_test:
    type: apiKey
    name: Authorization
    in: header
    x-amazon-apigateway-authtype: cognito_user_pools
    x-amazon-apigateway-authorizer:
      providerARNs:
        - >-
          arn:aws:cognito-idp:ap-northeast-1:xxxxxxxxxxxx:userpool/ap-northeast-1_yyyyyyyyyyyyyyy
      type: cognito_user_pools
      security:
        - jm_test: []

Lambda 関数

一番分かり易そうなのが、これかな。プロキシリソースを対象に、API Gateway で Lambdaプロキシ統合をする場合の Swagger 定義の例になります。

サンプルだけなら、これもいいかも。

必要なことは、 x-amazon-apigateway-integration を定義することになります。 uri に Lambda 関数のARNをベースにサンプルのように記述します。 この際、httpMethod は必ず POST にします(こちらのリファレンスのページの httpMethod に載ってますのでご確認ください)。

YAMLだとこんな感じになりました。

      x-amazon-apigateway-integration:
        uri: >-
          arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/(Lambda関数のARN)/invocations
        responses:
          default:
            statusCode: '200'
        passthroughBehavior: when_no_match
        httpMethod: POST
        contentHandling: CONVERT_TO_TEXT
        type: aws_proxy

なお、uri のフォーマットの説明はこれだ、というピンポイントのものがないのですが、

の2つのリファレンスの内容から、上記のような形式だと推測されます(pathの後ろのスラッシュ以降が Lambda 関数呼び出しAPIになってるようです)。 また、上記の例ではLambdaのバージョンは指定していないので、 Qualifier はつけていません。

CORS の設定

CORS も設定しておきましょう。こちらのサンプルがよくわかります。

API Gateway の API のインポートを使用してリソースで CORS を有効にする - Amazon API Gateway

まず、 CORS を有効としたいリソースに対して、 OPTIONS メソッドを定義します。 OPTIONS メソッドでは Mock 統合を指定します。 次に、 responses と x-amazon-apigateway-integration に 返すヘッダーの定義を追加します。サンプルの通りですね。

YAML だとこんな感じでした。

OPTIONS メソッドの定義

    options:
      tags:
        - CORS
      summary: CORS support
      description: Enable CORS by returning correct headers
      consumes:
        - application/json
      produces:
        - application/json
      responses:
        '200':
          description: 200 response
          schema:
            $ref: '#/definitions/Empty'
          headers:
            Access-Control-Allow-Origin:
              type: string
            Access-Control-Allow-Methods:
              type: string
            Access-Control-Allow-Headers:
              type: string
      x-amazon-apigateway-integration:
        responses:
          default:
            statusCode: '200'
            responseParameters:
              method.response.header.Access-Control-Allow-Methods: '''GET,OPTIONS'''
              method.response.header.Access-Control-Allow-Headers: >-
                'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
              method.response.header.Access-Control-Allow-Origin: '''*'''
        passthroughBehavior: when_no_match
        requestTemplates:
          application/json: '{"statusCode": 200}'
        type: mock

x-amazon-apigateway-integration の method.response.header.Access-Control-Allow-Methods は対象とするメソッド名を記述していますが、 '*' でも問題ないかと思います。

APIのレスポンスにヘッダ宣言を含めます。

  /api/sample/test:
    get:
      tags:
        - util
      summary: サンプル
      description: ''
      responses:
        '200':
          description: 成功時のレスポンス
          schema:
            $ref: '#/definitions/SuccessResponse'
          headers:
            Access-Control-Allow-Origin:
              type: string

x-amazon-apigateway-integration のレスポンスに Access-Control-Allow-Origin ヘッダに静的な値を設定します。

      x-amazon-apigateway-integration:
        uri: >-
          arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/(Lambda関数のARN)/invocations
        responses:
          default:
            statusCode: '200'
            responseParameters:
              method.response.header.Access-Control-Allow-Origin: '''*'''
        passthroughBehavior: when_no_match
        httpMethod: POST
        contentHandling: CONVERT_TO_TEXT
        type: aws_proxy

Lambda 関数を呼び出す権限の追加

以上を追加した Swagger ファイルを使って、API Gateway でインポートすると、無事に API が定義されます。一度 Swagger ファイルを作ってやれば何度でもインポートできるのでこれは便利ですね。

では、ということで、ステージにデプロイして、APIを呼び出すと、Lambda関数が呼び出せません。 なんでだろうか?と思い調べると、API Gateway に Lambda 関数を呼び出す権限が与えられていないから、ということでした。

いわれてみれば API Gateway のコンソールから Lambda 関数の呼び出しを設定する場合は、

f:id:junichim:20180824124228p:plain

のように、API Gateway に Lambda 関数を呼び出す権限を与えてもいいか?という確認画面がでて、これでOKを押すことで、Lambda関数を実行することができるとのことです(そんな画面あったなー、という感じですね)。

ということで、Lambda 関数の呼び出し権限を与える設定も行います。

今回は、Lambda 関数を呼び出す権限を持ったロールを定義し、これを Swagger で指定するという方法を取りたいと思います(ロールベースの権限設定)。

なお、API Gateway に必要な権限については、下記に詳しいと思います。

IAM アクセス権限により API へのアクセスを制御する - Amazon API Gateway

ロールの作成

API Gateway が Lambda 関数を呼び出す権限を持ったロールを作成したいと思います(実際の権限は下記チュートリアルに詳しいです)。

AWS Lambda 関数の API GatewayAPI を作成する - Amazon API Gateway

今回は、AWSのコンソールを使って作成したいと思います。

コンソールから、IAMを選択し、『ロールの作成』を選択します。

f:id:junichim:20180824124924p:plain

次に表示された画面で、『AWSサービス』を選択し

f:id:junichim:20180824125100p:plain

下のサービス一覧のリストから『API Gateway』を選択します。

f:id:junichim:20180824125221p:plain

このとき、画面下部の『ユースケースの選択』が選ばれた状態になっています。 『次のステップ』ボタンを押して、次の場面に進むと、ポリシーを設定できる画面になります。

f:id:junichim:20180824125401p:plain

が、ここでは、先ほどの『ユースケース』に対応するものしか選べないようで自由に選択することができませんでした。 なので、一旦このまま先に進みます。

f:id:junichim:20180824125631p:plain

適当なロール名を入力して、『ロールの作成』ボタンを押せば、ロールが作成されます。

次に作成されたロールを選択すると概要が表示されます。

f:id:junichim:20180824125847p:plain

この画面で『ポリシーをアタッチします』を選び、Lambda関数呼び出し権限を追加します。 フィルタを使って、Lambda関数呼び出しの権限『AWSLambdaRole』を探して、チェックをつけます。

f:id:junichim:20180824130047p:plain

『ポリシーのアタッチ』ボタンを押せば、ロールにLambda関数呼び出し権限が追加されます。

ロールの指定

ロールの指定方法は、

x-amazon-apigateway-integration オブジェクト - Amazon API Gateway

に記載されているように credential にロールのARNを指定すればよいようです。

YAML だとこんな感じになりました。

      x-amazon-apigateway-integration:
        credentials: 'arn:aws:iam::xxxxxxxxxxxx:role/yyyyyyyyyyyyyyyyyyyy'
        uri: >-

x-amazon-apigateway-integration に対して追記するので、Lambda 関数を呼び出す全てのAPIに対して設定が必要になります。

この設定を追加後、再度 Swagger ファイルをインポートして、作成した API にアクセスすると問題なく Lambda 関数が呼べました。

(参考)別の権限追加方法

リファレンスを調べてみると API Gateway からLambda 関数の呼び出しは、Lambda 側に設定を持たせることもできるようです(リソースベースの権限設定)。

AWS Lambda アクセス権限モデル - AWS Lambda

こちらの方法のほうが、どのAWSサービスから Lambda 関数が呼ばれているかを把握できるので便利だ、というようなことが書いてありました。

今回はこちらの方法はとらなかったので、機会があれば試してみたいと思います。

たねあかし

Swagger 拡張による記述は大変便利ですが、上記の記述方法を調べていくのって大変ですよね? 実は、上記に述べた Swagger 拡張は一からすべて調べて書いたのではなく、一旦手作業で API Gateway を設定後、ステージの『エクスポート』にある『Swagger + API Gateway 拡張でエクスポート』を実行し、作成されたファイル(Swagger拡張を含むAPI定義)を元に必要な記述を調べていきました。

f:id:junichim:20180824132738p:plain

こうすれば、設定したい機能に必要な Swagger 拡張のキーワード等が分かるので、そこから詳しく調べていくことができます。

ここまで設定ができると API の追加・変更があった場合も API Gateway 側で手作業で設定することが(現時点では)ほぼなくなるので非常に簡単になりました。 Swagger 拡張、なかなか使えますね。