やりたいこと
gae上で動いているアプリに対してService Workerを登録します。
やること
- service worker用のjsファイル
sw.js
を登録すること。 sw.js
ファイルのレスポンスヘッダーにService-Worker-Allowed:/
を登録すること。
手順
1. sw.jsを読み込むには、app.yaml
にてstaticファイル以下に配置した sw.js
を読み込むようにします。
- url: /sw.js static_files: static/js/pathTo/sw.js upload: static/js/pathTo/sw.js expiration: 0m
参考:
2. Service-Worker-Allowed: /
をsw.js
のレスポンスヘッダーに付与します。
// r *http.Request r.Request.Header.Set("Service-Worker-Allowed", "/")
これを static/
配下のroutingにセットします。
ちなみにFWのginなら以下のように書けます。
// main.go r := gin.New() g := r.Group("/static") g.Use(func(c *gin.Context) { c.Header("Service-Worker-Allowed", "/") c.Next() }) g.Static("", "./static")
これでgae上でservice workerを使うための sw.js
を登録することが出来ます。
追記
20180307
GAE上で動かしているアプリ上にservice-workerを登録するためのjsを仕込むには、service-worker登録用のjsをどうサーブするのかを考えないと行けないのですが、以下の場合に置いてGAE上のデプロイで sw.js
が404になってしまうという事象に遭遇しました。
- app.yamlファイルでsw.jsをサーブするpathを指定する。上記例では
/sw.js
としているところ。 - routingでstaticでファイルをサーブする箇所を指定して
Service-Worker-Allowed
ヘッダーを登録する。
skip_files.yamlやignore設定してない状態で sw.js
が上がるものかと思っていましたが、サーブ元が同じファイルをyamlとroutingの2箇所で同じ指定をしているとそのファイルにアクセスできませんでした。(多分上がっていない?)
これを解決するために
ということがわかりました。正常に sw.js
がアップロードされました。
参考にしたstackoverflowのエントリーはroutingでstaticファイルをサーブするなどを考慮していない純粋なGAEでのstatic file のサーブ方法だったので問題なかったですが、プロジェクトによってはstaticファイルのサーブをroutingで管理している場合もあると思うので、プロジェクトの状況に応じてsw.jsのサーブ方法を修正したほうが良いかもしれません。
サンプルコードには上げてしまったのですが、gin上で g.Static("", "./static")
でroutingにおいてstaticファイルのサーブ先を指定している場合は、yamlファイルでの設定は特にしなくても良く、ヘッダー情報だけ追加してService-Workerが動くpathを制限すればよかったということがわかりました。
routingでstaticファイルのサーブをしている場合と、yamlで指定する場合において、何かしら競合が起きると正常に動作しないということがわかりました。
おまけ
ginでのstaticファイルの指定方法について深掘りしてみました。
ginのStaicファイルの登録メソッドの定義は以下。
// Static serves files from the given file system root. // Internally a http.FileServer is used, therefore http.NotFound is used instead // of the Router's NotFound handler. // To use the operating system's file system implementation, // use : // router.Static("/static", "/var/www") func (group *RouterGroup) Static(relativePath, root string) IRoutes { return group.StaticFS(relativePath, Dir(root, false)) }
relativePath
: 相対パス。上記コードではGroupで/static
を指定してしまっているので特に意識しなくていいDir(root, false)
: ProjectRootのディレクトリから対象ディレクトリまでの絶対パスを指定する。trueにするとhttp.Dir()
と同じ動作をします。
以下のコードを見るとわかるけど true
のときは File, error
を返すようになります。
falseのときは File
のみ返します。
// @gin/fs type ( onlyfilesFS struct { fs http.FileSystem } neuteredReaddirFile struct { http.File } ) // Dir returns a http.Filesystem that can be used by http.FileServer(). It is used interally // in router.Static(). // if listDirectory == true, then it works the same as http.Dir() otherwise it returns // a filesystem that prevents http.FileServer() to list the directory files. func Dir(root string, listDirectory bool) http.FileSystem { fs := http.Dir(root) if listDirectory { return fs } return &onlyfilesFS{fs} }
// @http/fs // A Dir implements FileSystem using the native file system restricted to a // specific directory tree. // // While the FileSystem.Open method takes '/'-separated paths, a Dir's string // value is a filename on the native file system, not a URL, so it is separated // by filepath.Separator, which isn't necessarily '/'. // // An empty Dir is treated as ".". type Dir string func (d Dir) Open(name string) (File, error) { if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 || strings.Contains(name, "\x00") { return nil, errors.New("http: invalid character in file path") } dir := string(d) if dir == "" { dir = "." } f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))) if err != nil { return nil, err } return f, nil }
refs: