'Deploy to Heroku' Buttonで、Dockerイメージを利用してDeploy

'Deploy to Heroku' Buttonを使った際に、Dockerイメージを利用してDeployする方法がわかりづらかったのでメモ。

‘Deploy to Heroku’ Button

対象のリポジトリに用意するものは次の3つ。

  • heroku.yml
  • app.json
  • Dockerfile

heroku.yml

最低限の記述は次のような感じ。 ※Node.jsアプリの例

build:
  docker:
    web: Dockerfile
run:
  web: node dist/index.js

app.json

いつものように書けばいいが、stack にはcontainer を指定する。

{
  "name": "kakuyomu feed API",
  "description": "Providing kakuyomu feed API",
  "repository": "https://github.com/bagpack/kakuyomu-feed",
  "keywords": ["node", "kakuyomu", "feed"],
  "stack": "container"
}

このcontainerオプションはドキュメントになぜか書いていない。

Dockerfile

いつものように書く。

リポジトリ

以上を実装したリポジトリがこちらになります。

参考

Docker Builds with heroku.yml (beta) | Heroku Dev Center

Container Registry & Runtime (Docker Deploys) | Heroku Dev Center

暗黙的インテントを使用したときに選択されたアプリを知る

暗黙的インテントを使用したときにユーザがどのアプリを選択するか知るにはどうすればいいのか調査しました。 結論からいうと、Android 5.1以上から追加された次のAPIを利用することで、どのアプリを選択したかBroadcastReceiverで受け取ることができます。

public static Intent createChooser (Intent target, 
                CharSequence title, 
                IntentSender sender)

第三引数のIntentSenderはどうやって作るのかですが、PendingIntentgetIntentSender()というメソッドが用意されており、PendingIntentから取得するようです。

全体の流れは次になります。

  1. 選択された結果を受け取るためのBroadcastReceiverを用意する
  2. 受信先に1を指定したPendingIntentを用意する
  3. 発行したい暗黙的Intentを用意する
  4. 3のcreateChooserから取得したIntentをstartActivityに渡す

選択された結果を受け取るためのBroadcastReceiverを用意する

選択された結果を受け取るためのBroadcastReceiverを用意します。

class ChooseReceiver : BroadcastReceiver() {

  override fun onReceive(
    context: Context,
    intent: Intent
  ) {
    val componentName = intent.extras.get(EXTRA_CHOSEN_COMPONENT) as ComponentName
  }
}

manifestに追加することを忘れないようにします。

<receiver android:name="ChooseReceiver" android:exported="false"/>

受信先にさきほど作成したReceiverを指定したPendingIntentを用意する

val receiver = Intent(context, ChooseReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(context, 0, receiver, PendingIntent.FLAG_UPDATE_CURRENT)

発行したい暗黙的Intentを用意する

お好きな暗黙的Intentをご用意ください。

val share = Intent()
share.action = Intent.ACTION_SEND
share.type = "text/plain"
share.putExtra(
    Intent.EXTRA_TEXT,
    "テキスト"
)

createChooserから取得したIntentをstartActivityに渡す

    startActivity(Intent.createChooser(share
        , "タイトル"
        , pendingIntent.intentSender
    ))

以上です。

参考

Get results from Android Chooser – Code With Lisa – Medium

WISPrのSmart Clientを実装する

WISPrとは?

Wi-Fi based Wireless Internet Service Provider(WISP)のローミングについてのベストプラクティスです。 あくまでも、RFCのような標準ではなくベストプラクティスだそうです。

具体的には?

次のようなケースで使われています。

  1. 公衆無線LANに接続する
  2. 任意のURLにアクセスする
  3. ログインページ(Captive Portal)が表示される

このとき、Captive PortalのHTMLには次のようなXMLが埋め込まれています。

<?xml version="1.0" encoding="UTF-8"?>
<WISPAccessGatewayParam
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xsi:noNamespaceSchemaLocation="http://www.acmewisp.com/WISPAccessGatewa
yParam.xsd">
Alliance Wireless ISP Roaming

<Redirect>
<AccessProcedure>1.0</AccessProcedure>
<AccessLocation>12</AccessLocation>
<LocationName>
ACMEWISP,Gate_14_Terminal_C_of_Newark_Airport
</LocationName>
<LoginURL>http://www.acmewisp.com/login/</LoginURL>
<AbortLoginURL>
http://www.acmewisp.com/abortlogin/
</AbortLoginURL>
<MessageType>100</MessageType>
<ResponseCode>0</ResponseCode>
</Redirect>
</WISPAccessGatewayParam>

このXMLWISPrの仕様の一部です。

Smart Clientとは?

上記XMLを使ったやりとりを利用して、自動ログインの方法を提供することで、ユーザーの利便性を向上しようというのがSmart Clientになります。

WIPr 1.0の仕様書はなかなか読みづらいですが、Smart Clientを実装するだけであれば、Appendix D以降を読むだけで大丈夫です。 次にSmart Clientを実装するのに必要な情報をまとめました。

Smart Clientの実装

基本的な流れは次のとおりです。

  1. 任意のURLにGETリクエストを投げる
  2. (ログイン済みでなければ)ログインURLにリダイレクトされる
  3. ログインURLが返すHTML内にXMLが埋め込まれているので、XMLの内容にしたがって認証情報のリクエストを行う

フルスペックで実装するには次の1〜5の手続きを実装すればよさそうです。

  1. Login Request: Successful Case
  2. Login Request: Successful Case With プロキシ Reply
  3. Login Request: Successful Case With Polling
  4. Login Request: Reject
  5. Login Request: Reject With Polling

Login Request: Successful Case

ログイン正常系。 おそらく、日本の公衆無線LANの場合はこれを実装しておけばほとんどこと足りそう。

XMLのサンプル

 <?xml version="1.0" encoding="UTF-8"?>
 <WISPAccessGatewayParam
 xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
 xsi:noNamespaceSchemaLocation="http://www.acmewisp.com/WISPAccessGatewa
 yParam.xsd">
 <Redirect>
 <AccessProcedure>1.0</AccessProcedure>
 <AccessLocation>12</AccessLocation>
 <LocationName>
 ACMEWISP,Gate_14_Terminal_C_of_Newark_Airport
 </LocationName>
 <LoginURL>http://www.acmewisp.com/login/</LoginURL>
 <AbortLoginURL>
 http://www.acmewisp.com/abortlogin/
 </AbortLoginURL>
 <MessageType>100</MessageType>
 <ResponseCode>0</ResponseCode>
 </Redirect>
 </WISPAccessGatewayParam>

資格情報

次のパラメータをapplication/x-www-form-urulencoded形式で送信する。

パラメータ名 説明
UserName ユーザー名 example@domain
Pasword パスワード abcdefgh-123456
button ボタン(固定値) Login
FNAME フォーム名(固定値) 0
OriginatingServer もともとアクセスしようとしていた先のURL https://www.google.co.jp

Login Request: Successful Case With プロキシ Reply

Proxy Reply が返ってくるケース。 NextURLを挟んではいるが基本はLogin Request: Successful Caseと一緒。

XMLのサンプル

<?xml version="1.0" encoding="UTF-8"?>
<WISPAccessGatewayParam
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=
"http://www.acmewisp.com/WISPAccessGatewayParam.xsd">
<Proxy>
<MessageType>110</MessageType>
<NextURL>http://www.acmewisp.com/proxypoll</NextURL>
<ResponseCode>200</ResponseCode>
<Delay>5</Delay>
</Proxy>
</WISPAccessGatewayParam>

Login Request: Successful Case With Polling

即座に認証結果が返されずポーリングすることを要求されるケース。 Polling URLはレスポンスにLoginResultsURL が含まれていればLoginResultsURLを使用する。 含まれていなければLoginURLを使用する。

XMLのサンプル

<?xml version="1.0" encoding="UTF-8"?>
<WISPAccessGatewayParam
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xsi:noNamespaceSchemaLocation="http://www.acmewisp.com/WISPAccessGate
wayParam.xsd">
<AuthenticationPollReply>
<MessageType>140</MessageType>
<ResponseCode>201</ResponseCode>
<ReplyMessage>Authentication Pending</ReplyMessage>
<Delay>5</Delay>
<LoginResultsURL>http://wi2net/RLogin/1/</LoginResultsURL>
<LogoffURL>http://www.acmewisp.com/logoff/</LogoffURL>
</AuthenticationPollReply>
</WISPAccessGatewayParam>

Login Request: Reject

資格情報を送ったがログイン失敗するケース。

XMLのサンプル

<?xml version="1.0" encoding="UTF-8"?>
<WISPAccessGatewayParam
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xsi:noNamespaceSchemaLocation="http://www.acmewisp.com/WISPAccessGate
wayParam.xsd">
<AuthenticationReply>
<MessageType>120</MessageType>
<ResponseCode>100</ResponseCode>
<ReplyMessage>Invalid Password</ReplyMessage>
<LoginResultsURL>
http://www.acmewisp.com/loginresults/
</LoginResultsURL>
<LogoffURL>http://www.acmewisp.com/logoff/</LogoffURL>
</AuthenticationReply>
</WISPAccessGatewayParam>

Login Request: Reject With Polling

即座に認証結果が返されずポーリングすることを要求されて、ポーリングを繰り返すが、最終的にログイン失敗するケース。

リポジトリ

以上をSwiftで実装したのが次のリポジトリです。

Q&A

  • WISPrで使うユーザー名とパスワードはどうやって入手するの?

存じ上げません。

参考資料

Microsoft Word - WISPr-2003Jan.doc

wichert/wispr: Commandline WISPr client

iOS 9.0でSVGにtext-shadowのカンマ区切り記法を適用すると描画がおかしい問題

2018年にもなってiOS 9.0の記事を書くのはどうかしている感がありますね。

概要

iOS 9.0でSVGのTEXTタグにCSSでtext-shadowをカンマ区切りで複数適用すると、本来描画される文字列の下の方にもうひとつ文字列が描画されてしまうwebkitの不具合があります。

text-shadowをカンマ区切りで指定。

    .text-shadow {
      text-shadow: -2px -2px 0 #F00, 2px 2px 0 #F00;
    }

文字列が二重に表示される。

対策

下記のいずれかの対策がおすすめです。

  1. iOS 9を対象外にする
  2. SVGを使わない選択肢を考える
  3. text-shadowのカンマ区切りを使ったスタイルを諦める

しかし、何らかのしがらみでどうしても上記の対策を取れない場合は次のような対策方法があります。

  • 文字列を重ねて表示して、それぞれに別のtext-shadowを適用する
    <svg xmlns="http://www.w3.org/2000/svg"
         width="500" height="200" viewBox="0 0 500 40">
      <text x="0" y="35" font-size="35" class="text-shadow-1">
        Lower
      </text>
      <text x="0" y="35" font-size="35" class="text-shadow-2">
        Lower
      </text>
    </svg>

この方法には、セマンティックが壊れるという欠点があるので注意して使用する必要があります。

再現コード

下記は再現コードです。

JS Bin on jsbin.com

参考

webkitの類似バグ

https://bugs.webkit.org/show_bug.cgi?id=111216 https://bugs.webkit.org/show_bug.cgi?id=139810

NEHotspotHelperでAuthenticatingにフックする

NEHotspotHelperの情報は、開発に使用するのにも申請が必須というハードルの高さからか情報が少ない。 そんな中で貴重な情報源の次のページを参考にNEHotspotHelperを使ってみるとはまるケースがある。

[iOS] Wi-Fi一覧でアピール表示し、パスワードを自動入力して接続させる方法(とまとレストランの販売促進アプリ) | Developers.IO

はまったケース

f:id:bagpack:20170707104434p:plainApple Inc. online: Authentication State Machine

上図のAuthenticatingMaintainingの処理にフックしたい場合に、参考コードに処理を付け加える形で書くと、kNEHotspotHelperCommandTypeAuthenticateがいつまでも呼ばれない。 というのは、下記のコードをよくよく読んでみると、

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@"🍅🍅🍅とまとレストラン🍅🍅🍅", kNEHotspotHelperOptionDisplayName, nil];
    dispatch_queue_t queue = dispatch_get_main_queue();
    BOOL isAvailable = [NEHotspotHelper registerWithOptions:options queue:queue handler: ^(NEHotspotHelperCommand * cmd) {
        NSMutableArray *hotspotList = [NSMutableArray new];
 
        if(cmd.commandType == kNEHotspotHelperCommandTypeEvaluate || cmd.commandType == kNEHotspotHelperCommandTypeFilterScanList) {
            for (NEHotspotNetwork* network  in cmd.networkList) {
                NSLog(@">%@", network.SSID);
                if ([network.SSID isEqualToString:@"spw05"]) {
                    [network setConfidence:kNEHotspotHelperConfidenceHigh];
                    [network setPassword:@"password"];
                    [hotspotList addObject:network];
                }
            }
            NEHotspotHelperResponse *response = [cmd createResponse:kNEHotspotHelperResultSuccess];
            [response setNetworkList:hotspotList];
            [response deliver];
        }
    }];

[Developers.IO online: [iOS] Wi-Fi一覧でアピール表示し、パスワードを自動入力して接続させる方法(とまとレストランの販売促進アプリ) | Developers.IO]

kNEHotspotHelperCommandTypeEvaluateのときはcmd.networkListにはnilが入ってくるので、setNetworkListで空リストがセットされている。 そのため、対象のNetworkがNEHotspotHelperの管理対象から外れて、kNEHotspotHelperCommandTypeEvaluate以降のkNEHotspotHelperCommandTypeAuthenticateなどのコマンドが送られない状態となっている。

kNEHotspotHelperCommandTypeAuthenticateが呼ばれるために必要なこと

フォーラムの次の回答によると、

NEHotspotHelper (NetworkExtension iOS9.0) Sampl... | Apple Developer Forums

kNEHotspotHelperCommandTypeEvaluateのときに下記の処理をすればAuthenticatingステートが呼ばれるらしい。

  • コマンドからレスポンスを作製する(NEHotspotHelperResponse *response = [cmd createResponse:kNEHotspotHelperResultSuccess])
  • コマンドからnetworkを取得する(NENetwork *network = cmd.network)
  • networkにconfidenceを設定する(setConfidence)
  • レスポンスにnetworkを追加する(setNetwork)
  • レスポンスを配達する( [response deliver])

サンプルコード

以上を踏まえて、次のようなコードを書いてみると(ついでにSwift3に書き換え)、確かにauthenticateが呼ばれるようになった。 よかったです。

        let options : [String: NSString] = [
            kNEHotspotHelperOptionDisplayName: "🍅🍅🍅とまとレストラン🍅🍅🍅"
        ]
        
        let isAvailable = NEHotspotHelper.register(options: options, queue: DispatchQueue.main) { (cmd) in
            
            print(cmd)
            
            switch cmd.commandType {
            case .filterScanList:
                let list = cmd.networkList
                var networks = [NEHotspotNetwork]()
                for network in list! {
                    if network.ssid == "spw05" {
                        network.setPassword("password")
                        network.setConfidence(.high)
                        networks.append(network)
                    }
                }
                let response = cmd.createResponse(.success)
                response.setNetworkList(networks)
                response.deliver()
            case .evaluate:
                let network = cmd.network!
                let response = cmd.createResponse(.success)
                network.setConfidence(.high)
                response.setNetwork(network)
                response.deliver()
            case .authenticate:
                let network = cmd.network!
                let response = cmd.createResponse(.success)
                response.setNetwork(network)
                response.deliver()
            case .maintain:
                let network = cmd.network!
                let response = cmd.createResponse(.success)
                response.setNetwork(network)
                response.deliver()
            case .none:
                // anycode
                break
            case .presentUI:
                // anycode
                break
            case .logoff:
                // anycode
                break
            }
            
        }

Serverless Frameworkを使ってAWS LambdaにQRコードを返すAPIをデプロイする

サーバレスアーキテクチャAWS Lambda, Azure Functions, Google CloudFunctionsなど)の構成管理ができるServerless Frameworkを使ってみたかったので、任意の文字列をQRコード化して返すAPIを作ってデプロイしてみました。

環境は下記のとおりです。

本記事でわかること

Serverless Frameworkのインストール

$ npm install -g serverless
$ sls create --template aws-python --path qrcode-service

python環境の構築

$ cd qrcode-service
$ pyenv install 2.7
$ pyenv virtualenv 2.7 qrcode-service
$ pyenv local qrcode-service
$ pip install -r requirements.txt

requirements.txtは下記の通り。

olefile==0.44
Pillow==4.0.0
qrcode==5.3
six==1.10.0

QRコードを返す関数の作成

受け取った文字列をQRコード化して、base64エンコードした結果を返す関数を作成します。

handler.py

import base64
from io import BytesIO
import os
import sys

sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'vendor'))
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'vendor/PIL'))

import qrcode

def create(event, context):
    params = event['query']

    img = qrcode.make(params['text'])
    bufferd = BytesIO()
    stdout_buffer = getattr(sys.stdout, 'buffer', None)
    img.save(bufferd)
    image_base64 = base64.b64encode(bufferd.getvalue()).decode('utf-8')

    return image_base64

serverless.ymlを書く

基本的にはこちらのブログの記事の構成を再現する内容です。

Binary Support for API Integrations with Amazon API Gateway | AWS Compute Blog

serverless.yml

service: qrcode-service

provider:
  name: aws
  runtime: python2.7
  stage: dev
  region:  ap-northeast-1

package:
 exclude:
   - build_packages.sh
   - Dockerfile
   - requirements.txt

functions:
  qrcode:
    handler: handler.create
    events:
     - http:
         path: qrcode
         method: get
         integration: lambda
         request:
            parameters:
              querystrings:
                text: true
            passThrough: WHEN_NO_TEMPLATES
            template:
              image/png: |
                #define( $loop )
                  {
                  #foreach($key in $map.keySet())
                      #set( $k = $util.escapeJavaScript($key) )
                      #set( $v = $util.escapeJavaScript($map.get($key)).replaceAll("\\'", "'") )
                      "$k":
                        "$v"
                        #if( $foreach.hasNext ) , #end
                  #end
                  }
                #end

                {
                    #set( $map = $input.params().querystring )
                    "query": $loop
                }

ローカル実行で動作確認

Serverless Frameworkにはローカル実行する機能があるので、デプロイしなくても下記のようにして動作確認ができます。

$ sls invoke local -f qrcode -d '{ "query": { "text": "Kappa" } }' | sed 's/"//g' | base64 -d > kappa.png
$ open kappa.png

デプロイ用に外部ライブラリパッケージをまとめる

Lambdaで外部ライブラリを使う場合は外部ライブラリも含めてzipにする必要があります。 しかし、使用するライブラリにネイティブバイナリが含まれている場合は、MacでビルドしてしまうとDarwin用のモジュールになってしまってLambda上では動作しません。 今回は、MacもEC2もCPUは同じx64系ということで、amazonlinuxのDocker Imageを使ってMac上でビルドしてしまうことにします。

Docker Imageのビルド

Dockerfileは下記の通りです。

FROM amazonlinux

WORKDIR /work

RUN set -x && \
    yum install -y python27-devel python27-pip gcc gcc-c++ cmake && \
    yum install -y libtiff-devel libjpeg-devel libzip-devel freetype-devel \
      lcms2-devel libwebp-devel tcl-devel tk-devel && \
    pip install --upgrade pip

ビルドします。

$ docker build . -t qrcode-service

外部ライブラリのビルド

外部ライブラリをdocker container上でビルドします。

$ mkdir vendor
$ cp requirements.txt vendor
$ docker run -v "$(pwd)"/vendor:/work qrcode-service pip install -r requirements.txt -t .

AWSにデプロイする

Serverless Frameworkのコマンド一発で完了……といきたいところですが、Serverless Frameworkがバックエンドで使用しているCloudFormationがバイナリサポートの設定に対応していないらしく、コマンド一発で完了とはいきませんでした。 しかたがないので一部の設定は別途行います。

1. まずはデプロイする

下記のコマンドでデプロイを実行します。

$ sls deploy
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3 (6.26 MB)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............................
Serverless: Stack update finished...
Service Information
service: qrcode-service
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  GET - https://u5uko0b1ne.execute-api.ap-northeast-1.amazonaws.com/dev/qrcode
functions:
  qrcode: qrcode-service-dev-qrcode

上記の例だとu5uko0b1neAPI IDになるので控えておきます。

デプロイが完了した時点でで下記のようにリクエストを送るとbase64エンコードされたまま結果が返ってくるのが確認できます。

$ curl -X GET -H "Accept: image/png" -H "Content-Type: image/png" "https://u5uko0b1ne.execute-api.ap-northeast-1.amazonaws.com/dev/qrcode?text=Kappa"
"iVBORw0KGgoAAAANSUhEUgAAASIAAAEiAQAAAAB1xeIbAAABhklEQVR4nO2YTW6DMBCFv6m9d26Qo5gb9Ky9AT5KDhAJ9kbThW3kkkrthvDnWSBiPoknZzR+PFH+rvDxDwga1ahGNapRe6ckl0W60QJjWek21XUJyquq6gDaA9JhVFVVf1Lv13UJaiw9Hu4x/wEidntdZ6bs4rfgJvviO/eq/mSUf1ike+cbL0uVvncKjACYKAB18+9V/bGpvPdBADDz+iRrvbFRpWQx2wVMXEItU1iRkg6QbpTU7dnb3/KDvas/KkXy8T1GtXdaXL1TxQ/z072qPzaV5r3gnoJ/2Ejo5pnjYjmJ96r+2FTaXWW06bOKfMxmky++30bXFahq5uQhM0CaPr2LbeasSeXdnQs/QH1pe78aRR2ZYbRea33/FqrOMY0SbpPgB6PSbavr1FRxMuMN/BcIgOCMEm7PkqrtVf25KFWN4B8idaq2va4zUssMmfA5QLgrkvK1jXRdiXKq2qfbSSqfs7WuM1OvPsfFdOBmy9l8zmrUMsf8tVqO2ahGNapRh6e+AZkLzhog49P2AAAAAElFTkSuQmCC"

2. バイナリサポートの有効化

API Gatewayがリクエストの送受信でバイナリデータを扱えるようにバイナリサポートを有効にします。

$ aws apigateway update-rest-api \
--rest-api-id u5uko0b1ne \
--patch-operations op=add,path=/binaryMediaTypes/image~1png

u5uko0b1ne はそれぞれのAPI IDで置き換えるようにしてください。

3. API GatewayにLambda関数を呼び出す権限を与える

手動で作ると問題ないのに、CloudFormationで構成したときに上手く動かないので悩んでいたのですが、次の記事によるとCloudFormationで構成した場合は、AWS Consoleから権限を与える必要があるそうです。(他に方法はないのでしょうか?)

krisgholson/serverless-thumbnail: Recreate the thumbnail service described here .. https://aws.amazon.com/blogs/compute/binary-support-for-api-integrations-with-amazon-api-gateway/ .. using the serverless.com framework (and document some gotchas).

4. 再度のデプロイ

再度、デプロイします。

$ sls deploy
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3 (6.26 MB)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............
Serverless: Stack update finished...
Service Information
service: qrcode-service
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  GET - https://u5uko0b1ne.execute-api.ap-northeast-1.amazonaws.com/dev/qrcode
functions:
  qrcode: qrcode-service-dev-qrcode

APIを叩いてみると今度はバイナリデータが返ってくることが確認できました。

$ curl -X GET -H "Accept: image/png" -H "Content-Type: image/png" "https://u5uko0b1ne.execute-api.ap-northeast-1.amazonaws.com/dev/qrcode?text=Kappa"
�PNG

IHDR""u���IDATx��Mn�0����wn�����>J        �FӅm�J���Y b>�'g4~<Q���ըF5�Q{�$�E��cY�6�u        ʫ����aTUU�R��u        j,=�1�"v{]g����&��;��d��X�{�K��w
��(u��U��A0��$k��Q�d1�L\B-SX���n������򃽫?*E��=F�wZ\�S��ӽ�?6�������H���b9����T�]e�鳊|�f�/��F��j��!3@�>��m�I�ݝ
                                                                                                           ?@}i{�E�a�^k}���1�n����nϒ��U�(U��"u����3R�
                     ��9@�+��t]�r�ڧ�I*����3S�>��t�f��|�j�2��Z�٨F5�Q����
                                                                       � ���IEND�B`�i

以上です。

リポジトリ

完成品が下記になります。

bagpack/serverless-framework-qrcode-service: Sample for Binary Support using API Gateway REST API with Serverless Framework.

参考

Binary Support for API Integrations with Amazon API Gateway | AWS Compute Blog

krisgholson/serverless-thumbnail: Recreate the thumbnail service described here .. https://aws.amazon.com/blogs/compute/binary-support-for-api-integrations-with-amazon-api-gateway/ .. using the serverless.com framework (and document some gotchas).

python-qrcodeのマスク処理結果の評価方法が若干間違っている

PythonQRコードを生成するのに便利なライブラリ

lincolnloop/python-qrcode: Python QR Code image generator

このライブラリはマスク処理結果の評価方法が若干仕様と異なっているため、他のライブラリとはできあがったQRコードが違う結果になることがある。 間違っているのはQRコードの仕様書の次の部分。

1:1:3:1:1 比率パターンの前又は後ろに比率4の幅以上の明パターンが存在する。 qrcode_specification_ja

python-qrcodeの該当部分は下記になるが、1:1:3:1:1 比率パターンの出現自体を評価してしまっている。 本来は1:1:3:1:1 比率パターンの前か後ろに比率4の幅以上の明パターンが存在することを評価すべきである。

def _lost_point_level3(modules, modules_count):
    modules_range_short = xrange(modules_count-6)

    lost_point = 0
    for row in xrange(modules_count):
        this_row = modules[row]
        for col in modules_range_short:
            if (this_row[col]
                    and not this_row[col + 1]
                    and this_row[col + 2]
                    and this_row[col + 3]
                    and this_row[col + 4]
                    and not this_row[col + 5]
                    and this_row[col + 6]):
                lost_point += 40

    for col in xrange(modules_count):
        for row in modules_range_short:
            if (modules[row][col]
                    and not modules[row + 1][col]
                    and modules[row + 2][col]
                    and modules[row + 3][col]
                    and modules[row + 4][col]
                    and not modules[row + 5][col]
                    and modules[row + 6][col]):
                lost_point += 40

    return lost_point

一方、golangQRコードのライブラリ

skip2/go-qrcode: QR Code encoder (Go)

では、下記のようになっており、1:1:3:1:1 比率パターンの出現だけではなく、その前後に比率4の幅以上の明パターンが存在するかどうかまできちんと評価している。

func (m *symbol) penalty3() int {
    penalty := 0

    for y := 0; y < m.symbolSize; y++ {
        var bitBuffer int16 = 0x00

        for x := 0; x < m.symbolSize; x++ {
            bitBuffer <<= 1
            if v := m.get(x, y); v {
                bitBuffer |= 1
            }

            switch bitBuffer & 0x7ff {
            // 0b000 0101 1101 or 0b10111010000
            // 0x05d           or 0x5d0
            case 0x05d, 0x5d0:
                penalty += penaltyWeight3
                bitBuffer = 0xFF
            default:
                if x == m.symbolSize-1 && (bitBuffer&0x7f) == 0x5d {
                    penalty += penaltyWeight3
                    bitBuffer = 0xFF
                }
            }
        }
    }

    for x := 0; x < m.symbolSize; x++ {
        var bitBuffer int16 = 0x00

        for y := 0; y < m.symbolSize; y++ {
            bitBuffer <<= 1
            if v := m.get(x, y); v {
                bitBuffer |= 1
            }

            switch bitBuffer & 0x7ff {
            // 0b000 0101 1101 or 0b10111010000
            // 0x05d           or 0x5d0
            case 0x05d, 0x5d0:
                penalty += penaltyWeight3
                bitBuffer = 0xFF
            default:
                if y == m.symbolSize-1 && (bitBuffer&0x7f) == 0x5d {
                    penalty += penaltyWeight3
                    bitBuffer = 0xFF
                }
            }
        }
    }

    return penalty
}

しかし、適用されるマスクパターンが間違っているぐらいではQRコードが読めなかったりはしないので実用上は問題なかったりする。 (読みにくくなったりするケースはあれど)

追記 2017/03/31

この件を修正するPull Requestが送られているようだ。

Change penalty rules. Small optimization. Add optional argument mask_pattern. by cryptogun · Pull Request #127 · lincolnloop/python-qrcode