WebPushについて簡単整理
WebPushについて私なりに理解したことを最大限簡単に書いてみます。
WebPushとは
ブラウザに対してサーバからプッシュ通知ができる技術です。
他のウェブページを見ていても通知が来ます。ブラウザによっては、ブラウザを立ち上げなくても通知が来ます。
専用アプリは不要です。ウェブページ(html, jsp...)とjsだけでプッシュ通知できます。
通知がたくさん来ると、ユーザに迷惑でしょうが、うまく使えば色々な使い方ができそうですね。
プッシュ通知の流れ
プッシュ通知は以下の流れで動作します。
標準のWebPush APIを利用すると仮定します。
AP ServerはApplication ServerでTomcatなどのコンテナーを表します。
ユーザ > Browser
- ユーザがBrowserでウェブページにアクセスします。
- Browserからプッシュ通知を許可するかどうかの選択を求めるダイアログが表示されます。
- ユーザが許可を選択します。
Browser > PushServer > Browser
- ユーザがBrowserでプッシュ通知の購読ボタンを押します。
- BrowserからPushServerに購読をリクエストします。
- PushServerはEndPoint(URL)をBrowserに返します。
Browser > AP Server > DB
- BrowserはPushServerから受け取ったEndPointとユーザキーなどをAP Serverに送ります。
- AP ServerはEndPointとユーザキーなどをDBに保存します。
AP Server > PushServer > Browser
- AP Serverの使用者はEndPoint宛にメッセージを送り、メッセージはPushServerに届きます。
- PushServerはBrowserにメッセージ送り、Browserはメッセージを通知ダイアログで表示します。
PushServer
ブラウザの利用者にプッシュ通知を送るためにはEndPointというURLにメッセージを送る必要があります。
EndPointはPushServerのURLを表し、ユーザごとにユニークでドメインもブラウザごとに違います。
WebPushを標準的な方法で利用すると、特にEndPointが何者か意識する必要はありません。
Push APIの購読メソッドを呼び出すと、EndPointが返ってきますが、そのEndPoint宛にメッセージを送れば、
該当ブラウザにプッシュ通知が表示されます。
サポートするブラウザ
Chrome、Firefox、SafariがWebPushをサポートします。
ブラウザのバージョンは大体2016年7月以降のものなら、WebPushの標準的な使い方ができます。
ChromeとFirefoxは検証できましたが、SafariはMacがないので、分かりません。
またSafariは少々サポート(アイコン未サポート..)が足りないようです。
MicrosoftのEdgeは開発中だそうです。
従いまして、今のところChromeとFirefoxが確実にサポートすると考えればいいと思います。
実装方法の違い
Firefoxでは特に意識する必要はありません。
Chromeの場合、バージョン52未満ではChrome専用で行わなければならないもの(例 : ID取得、manifest.jsonファイル)があります。
今はChrome 52以降なら、Web Pushプロトコル標準をサポートしているため、ブラウザ依存なしで実装できます。
より多くのブラウザをサポートするためにChromeの旧バージョンもサポートすべきか?と悩むところですが、
私は必要ないのでは?と思います。
なぜかというと、IEならまだしも、ChromeとFirefoxは知らぬ間に自動更新してくれますし、
ブラウザシェアを見ると、ほとんどのChromeユーザは52以降のバージョンを使用中だからです。
(WebブラウザシェアランキングTOP10(日本国内・世界) – ソフトウェアテスト・第三者検証ならウェブレッジ)
ですので、近々なくなるChrome依存のコードは要らないのでは?と思います。
サンプル
以下のgithubのサンプルが役立ちました。
Javaベースですが、標準的なAPIを使用しているので、他の言語への適用は問題ないと思います。
github.com
WebPushの標準的な実装だけでなく、古いバージョンのChromeにも対応しています。
暗号化する部分が複雑ですが、ただWebPushを利用するだけなら、スキップし、実際通信する部分だけを参照しても問題ないと思います。
実装ポイント
- プッシュ通知の許可や購読を行うjsは該当ウェブサイトと別のドメインにあってもいい。
- 通知を行うjsは該当ウェブサイトに配置する必要がある。
- 通知を行うjsはブラウザでService Workerとして常駐することになる。つまり、該当ウェブサイトを去ってもずっとブラウザのメモリに残って通知が来ることを待つ。
- Service Workerとして登録された通知を行うjsの更新に注意する必要がある。期限を設定し、うまく更新されるか確認しよう。
SPFとSenderIDで曖昧な部分を整理
SPFについてブログやドキュメントを読んでも何を言っているのか分からないところを整理しました。
実際に検証したところもあり、英文を探して分かったところもあります。
SPFとSenderIDはどのドメインを確認するか
- SPFはMAIL FROMコマンド(いわゆるEnvelope From)に指定されるメールアドレスのドメイン部のSPFレコードをチェックします。
- SenderIDはPRA(Purported Responsible Address)といって、メールヘッダーにある最終的に責任があるメールアドレスのドメイン部のSPFレコードをチェックします。
下記はRFC4407でPRAを求めるアルゴリズムのStep 1からStep 6までを疑似コードで書いてみたのです。
def getPRA def step2_to_step6 # step 2 if Resent-From.isEmpty if Sender.isEmpty # step 4 if From.size == 1 return From[0] else throw new Exception end else # step 3 if Sender.size == 1 return Sender[0] else throw new Exception end end else return Resent-From end end # step 1 if Resent-Sender.isEmpty return step2_to_step6() else if Resent-From.isBefore(Resent-Sender) && (Received.isAfter(Resent-From) && Received.isBefore(Resent-Sender)) || (Return-Path.isAfter(Resent-From) && Return-Path.isBefore(Resent-Sender)) return step2_to_step6() else return Resent-Sender end end end
step1で条件が複雑ですが、Resent-Sender > Resent-From > Sender > Fromの順にチェックしていて、通常はFromのメールアドレスがPRAになると考えればよさそうです。
includeとredirectの違い
example.org. IN TXT "v=spf1 include:_spf-a.example.com include:_spf-b.example.com include:_spf-c.example.com -all"
example.net. IN TXT "v=spf1 redirect=example.org"
- まとめると、includeとredirectの違いは自分のSPF設定を追加できるかどうかです。
メールログの時間がずれる時の対処
メール送信と受信のため、PostfixとDovecotをインストールしましたが、
メールログ(/var/log/maillog)に出力される時間がサーバ時間と9時間もずれていました。
色々ググって試して失敗したりしましたが、ついに解決できましたので、メモします。
以下のコマンドを実行したら、いけました。rootで行う必要があります。
cd /var/spool/postfix mkdir etc #既にあれば、スキップ cd etc cp /etc/localtime . service postfix restart service rsyslog restart
殆どのブログでは/etc/localtimeを/var/spool/postfix/etcにコピーすればいいと言っていましたが、
私が使うサーバではrsyslogの再起動も必要でした。
セッションストアに関してメモ
最近、RoR(Ruby on Rails)を勉強しています。
RoRでセッションデータはデフォルトでブラウザのクッキーに保存されます。
最初はServletのようにセッションIDだけ、ブラウザに送られて、実際のデータはサーバのメモリに保存されると思いました。
しかし、そうではなく、セッションに入れたすべてのデータがブラウザのクッキーに保存されるのでした。
これはまずいんじゃない?と思いました。
なぜなら、たとえ、クッキーのセッションデータが暗号化されていても理論的には復号化できるので、
セキュリティ上、安全ではないからです。
幸い、RoRではセッションストアを変更できます。
セッションデータをDBはもちろんのこと、Redisやmemcachedなどのキャッシュサーバに保存することも可能です。
RoRで本格的にウェブアプリケーションを開発する時にはセッションストアをクッキー以外に変更しましょう。
以下のgemはセッションデータをDBに保存します。
github.com
以下はredisです。
github.com
最後にmemcachedです。
github.com
どのセッションストアもセッションデータを一つのAPサーバのメモリでなく、クッキーやDBなどの共有ストレージに保存するとの共通点がありますね。
こうなると、APサーバの冗長化時にセッションを同期するためにクッキーにサーバのIDを送るとか、セッションレプリケーションなどを行う必要がないから、便利だと思います。
勉強メモ1
Rubyは以前遊びぐらいでしか触ったことがありませんが、
今回勉強したいと思い、学習を始めることになりました。
ただ、以前のように分厚い本を買って、表紙(まえがき)から最後の隅々まで
読んで写経することはしないつもりです。
時間を節約し、効率的に勉強したいと思います。
その方法として、既存のプログラミング言語に共通しそうな部分は除いて
Rubyにしかないものだけを勉強する、差分学習を取ります。
この記事はその学習メモです。
文字列中の変数
"#{変数名}"
コメント
- 1行 : #
- ブロック =begin ~ =end
定数
大文字。変更不可能。
多重代入
q, a, z = 1, 2, 3 q, a, *z = 1, 2, 3, 4, 5 # zには配列として代入。1, 2, [3, 4, 5] q, a = 0, 1 q, a = b, z ar = [3,4] q,a = ar # 3, 4 ar = [1, [2, 3], 4] q, (a1, a2), z = ar # 1, 2, 3, 4
分岐
if修飾子とunless修飾子
文の右にifやunlessを書くことができる。ミニif文、ミニunless文ってことか。
a = 1 "Hi" if a > 1 # nil "Man" if a == 1 # Man "Hoi" unless a > 1 # Hoi
===
==に似ているが、左辺がリテラル(クラス、数値、文字列など)の場合、右辺との比較を柔軟に行う。
例えば、String == "a"だと、false。String === "a"だと、"a"がStringクラスのインスタンスという比較を行い、trueがリターンされる。
同一性
オブジェクトの値を比較するなら、==を使えばいい。厳密にオブジェクトの同一性を比較するなら、eql?を使う。
Javaとは反対ですね。
繰り返し
- untilはwhileの反対。
- nextはJavaのcontinue。
- redoは同じことをもう一回繰り返す。
times
10.times do print "Duke" end
配列
names = ["Duke", "Foo"] names.each do |n| puts n end
ハッシュ
person = { name: "Duke", job: "Engineer" } person.each do |key, value| puts "#{key}: #{value}" end
ブロック
1行にまとめるときは{ ... }、複数行に分ける場合、do ... end
メソッド
- 最後にreturn文は省略してもよい。
- 可変長引数は引数に*argsと書く。
キーワード引数
引数名:デフォルト値
def m1(q:1, a:2, z:3) q + a + z end m1(z:5,q:3) # 10
ブロック付きメソッド
def m1 puts "Hello!" yield # これがブロックを実行する。 end m1 do puts "Hey Duke!" end # Hello! # Hey Duke!
引数がハッシュの場合、{}を省略可能
def m(q) q end m({ "n" => "d", "t" => 5 }) # { "n" => "d", "t" => 5 } m("n" => "d", "t" => 5) # { "n" => "d", "t" => 5 } m(n: "d", "t": 5) # { "n" => "d", "t" => 5 }
クラス
アクセスメソッド
getter, setterのようなもの。
一々メソッドを定義してもいいが、getter/setterの数が多いと、面倒。
代わりに以下のものを使えばgetterとsetterを自動生成してくれる。
attr_reader :変数名
getter
attr_setter :変数名
setter
attr_accessor :変数名
getter + setter
クラスメソッド
class << オブジェクト ~ end形式がある。
オブジェクトにクラス名を指定すると、以下の例のようにクラスメソッドになる(Classクラスのインスタンスにメソッドを追加)。
しかし、オブジェクトに特定インスタンス変数を指定すると、そのインスタンスのみにメソッドが追加される。
まるでJavascriptでオブジェクトに自由にメソッドを追加できるのと同じ感じだ。
# クラス内で定義 class DukeLab class << self def classMethod1 "Hey" end end end DukeLab.classMethod1 # Hey # 特異クラス形式で定義。既にDukeLabクラスが定義されなければならない。 class << DukeLab def classMethod2 "You!" end end DukeLab.classMethod2 # You! # クラスの外から定義 def DukeLab.classMethod3 "Man!" end DukeLab.classMethod3 # Man! # クラス内で定義 class DukeLab def self.classMethod4 "Woman!" end end DukeLab.classMethod4 # Man!
定数
クラス名::変数名
アクセス制限
何も指定しなければ、public。
public, private, protectedキーワードを使ってアクセス制限を定義できる。
class DukeTest def m1 "Duke1" end public :m1 #デフォルト def m2 "Duke2" end private :m2 end dt = DukeTest.new dt.m1 dt.m2 # エラー
まとめてprivateにする
class DukeTest def m1 "Duke1" end private # これより下はprivate def m2 "Duke2" end end
getだけ可能にする。
class DukeTest attr_accessor :q, :a private :q=, :a= # xとyにsetできないようにする。 def initialize(q, a) @q, @a = q, a end end dt = DukeTest.new dt.q dt.q = 8 # エラー
継承
class クラス < スーパークラス
モジュール
Scalaのtrait、Javaのdefault interfaceって感じ?
クラスでinclude モジュール名すると、そのクラスにモジュールの全てのメソッドが追加される。
モジュールをクラスに入れることをMixinという。
module Mod1 def m1 "variable : #{@q}" end end class Duke include Mod1 def initialize(q) @q = q end end d = Duke.new(5) d.m1 # variable : 5
Object.extend
特定オブジェクトにモジュールをMixinする。
module Mod1 def m1 "variable : #{@q}" end end class Duke def initialize(q) @q = q end end d1 = Duke.new(8) d1.extend(Mod1) d1.m1 # variable : 8
extendでクラスメソッドを追加
module Mod1 def m1 "Hi!" end end module Mod2 def m2 "Man!" end end class Duke extend Mod1 #クラスメソッド include Mod2 #インスタンスメソッド end d1 = Duke.new d1.m1 # エラー。インスタンスメソッドではない。 Duke.m1 # Hi! d1.m2 # Man! Duke.m2 # エラー。クラスメソッドではない。
演算子
範囲演算子
Range.new(x, y)又はx..yを使う。
for i in 1..10 puts i end (1..10).to_a # 1,2,3,4,5,6,7,8,9,10 (1...10).to_a # 1,2,3,4,5,6,7,8,9 yを除く。
演算子定義
class Apple attr_reader :n, :p def initialize(n, p) @n = n @p = p end def +@ # 単項演算子の場合、後尾に@をつける。 "Plus" end def +(other) self.class.new(n + other.n,p + other.p) end def inspect "n : #{n}, p : #{p}" end end a = Apple.new(1, 10) b = Apple.new(2, 20) +a # "Plus" a + b # n : 3, p : 30
例外
キーワードが違うだけで殆どJavaと同じ。
begin # try ... rescue => ex # catch ... retry # beginに戻って再実行。無限ループに注意。 ensure # finally ... end
メソッド全体をtry-catchしたい場合、beginとendは省略可能。
def duke ... rescue => ex # catch ... retry # beginに戻って再実行。無限ループに注意。 ensure # finally ...
例外のスローはraiseキーワードを使う。
rescue修飾子
A rescue Bで、Aが失敗すると、Bが実行される。ミニtry-catchのようなもの。
File.open("duke.txt") rescue "-_-Error" # -_-Error
ブロック
処理のかたまり。
メソッドに何かを実行するブロックを渡すと、前後処理は自動でやってくれるケースが多い。
例えば、以下の場合、ファイルをオープン(前処理)し、その内容を一行ずつ表示する。
ファイルを閉じる処理(後処理)は自動でやってくれる。
File.open("README.md") do |file| file.each_line do |line| print line end end
ブロックの実行
ブロックはメソッドでyieldコマンドで実行する。
yieldコマンドに引数を指定すると、ブロックに渡される。
def m1(q) v = yield(q + 1) puts "value : #{v}" end m1(5) { |n| "wow => #{n}" } # value : wow => 6 m1(7) do |n| # value : haha => 7 "haha => #{n}" end
制御
break, next, redoをブロック内でも使うことができる。
def m1(q) puts "Step1" yield(q) puts "Step2" yield(q + 1) puts "Step3" yield(q + 2) end m1(1) do |q| # Step1 Block Step2 Block Step3 Block => 3!!! if (q == 5) break end puts "Block" "#{q}!!!" end m1(4) do |q| # Step1 Block Step2 => nil if (q == 5) break end puts "Block" "#{q}!!!" end # breakやnextに戻り値を指定できる。 m1(4) do |q| # Step1 Block Step2 => Break! if (q == 5) break "Break!" end puts "Block" "#{q}!!!" end m1(4) do |q| # Step1 Block Step2 Step3 Block => 6!!! if (q == 5) next end puts "Block" "#{q}!!!" end
redoは無限ループの危険がありそうだ。
Proc
ブロックをオブジェクトにしてくれるもの。"ブロックをProcオブジェクトにする"と言い方をするらしい。
Procオブジェクト化したブロックはcallメソッドで呼べる。
def m1(q, &b) # &はブロックをProcオブジェクトに自動変換する。 v = b.call(q) "yaho!#{v}" end m1(5) do |q| # yaho!haha5 "haha#{q}" end myb = Proc.new do |q| "haha2#{q}" end m1(8, &myb) # yaho!haha28 m1(8, myb) # エラー。&をつける必要がある。 def m2(q, b) v = b.call(q) "kaho!#{v}" end myb2 = Proc.new do |q| "kaha2#{q}" end m2(9, myb2) # kaho!kaha29 #別の書き方 : procメソッド myb3 = proc do |q| "kaha3#{q}" end m2(10, myb3) #kaho!kaha310 #別の書き方 : lambda myb4 = lambda do |q| "kaha4#{q}" end m2(11, myb4) #kaho!kaha411 #別の書き方 : -> myb4 = ->(q) { "kaha5#{q}" } m2(12, myb4) #kaho!kaha512 #procとlamdbaの違い1 : lamdbaの方が引数のチェックが厳密だ。 myb5 = proc do |q,a| print [q, a] end myb6 = lambda do |q,a| print [q, a] end myb5.call(2) # [2,nil] myb6.call(2) # エラー : ArgumentError: wrong number of arguments (1 for 2) #procとlamdbaの違い2 : procを作ったメソッド外でproc内でreturnすると、エラーになる。 def m1 proc { return 1 } end def m2 lambda { return 2 } end p = m1 p.call # エラー(procが自分を作ったm1の外で呼びだされ、returnしたため) : LocalJumpError: unexpected return d = m2 d.call # 2
今日はここまで。
webapps配下でないディレクトリにリソースを置く
Tomcatで、ウェブアプリで使われるリソースをwebapps配下でないディレクトリに置きたい場合あります。
例えば、リソース(画像やJSPなど)をアップロードしてウェブアプリの画面を動的に構成したい時です。
またお客様がウェブアプリ本体のソースを修正することなく、カスタマイズできるようにしたい時もありますね。
もし、そういうリソースをwebapps配下におくと、
ウェブアプリをデプロイするたびに既存のリソースが消えてしまうので、困りますね。
こういう場合、Tomcat8に追加されたResources Componentを使えばいいです。
以下のようにserver.xmlのContext要素配下にResources要素とその下のPostResources要素を追加します。
<Context docBase="myweb" path="" reloadable="false"> <Resources allowLinking="false"> <PostResources className="org.apache.catalina.webresources.DirResourceSet" base="${other}/myres" webAppMount="/myres" /> </Resources> </Context>
PostResourcesのbaseにはリソースが位置する絶対パスを指定します。
${other}のようにJVMのシステムプロパティを指定することもできます。
webAppMountにはURLのパス(ウェブアプリのルートパスで次の部分)を指定します。
上記の場合、URLの後ろに/myresと入力した場合、
baseディレクトリにあるリソースを読み込んでレスポンスとして返します。
リソースの位置がwebappsでないだけで、既存webapps配下にあるリソースとまったく同じです。
良い機能ですね。
機械学習の種類とR言語でのパッケージ
機械学習の種類とR言語でのパッケージをざっくりと整理してみました。
自分の理解に役に立つ形態で整理したため、書籍に出るようなまともなものとは違うところがあります。
また、間違った部分もありえますので、気付き次第、修正していくつもりです。
予測
分類(Classification)
データを決まったカテゴリにグループ分けすることです。
kNNアルゴリズム
classパッケージのknn関数
決定木
C5.0アルゴリズムの実装では、C50パッケージのC5.0()関数
回帰(Regression)
数値の予測を行うことです。
線形回帰
statsパッケージのlm(関数)
複数用途
神経網
人間の脳を構成するニューロンの仕組みをコンピューターで真似て予測を行います。
Neuralnetパッケージのneuralnet()関数。
SVM(Support Vector Machine)
kernlabパッケージのksvm()関数。