u-ryo's blog

various information for coding...

Category: Jhipster

Dont Use the Same Image File in Css and Manifest.webapp on Webpack

| Comments

notificationに使っていたlogicを変えたいと思って、 アプリとしてinstallしていたPWAを一旦uninstallしてから browserでそのpageを読んでも、「ホーム画面に追加」が現れなかったんですね。 何でんだろー、一旦uninstallしちゃうとダメなのかな? いやそんなことないだろうな、とは思いつつも 明確に「uninstallしても大丈夫」だと書いてあるところもなく、 心配だったんです。 「ホーム画面に追加」が出ないのは何が悪いのか、わからないんですよね。

「ホーム画面に追加」が出る条件、というのがあって、 公式ページ(ウェブアプリのインストール バナー)によると、

  1. short_name(ホーム画面で使用)
  2. name(バナーで使用)
  3. 192x192のpngアイコン(アイコンの宣言にはMIMEタイプimage/pngの指定が必要)
  4. 読み込み先のstart_url

というんですが、 「ホーム画面に追加」からはじめる『PWA(Service Worker)』には192のpngの話はなく、 また【3ステップではじめる】PWAによる「ホーム画面に追加」バナーの実装ではfetch eventの定義が必要だとあります。 こちとら、一度は出来たわけだし、実際には PWAで「ホーム画面に追加」が表示されない時に確認する事にある「manifest.json内のアイコン画像が404である」といったところが一番多いんじゃないでしょうか。 この人もそうだったといいますし、ぼくもそうでした。

ただ、その原因は困ったものでした。 webpackでhash化された名前の付いたpng fileが壊れていたからです。 とても悩みました。調べてもそんな事例、出てきません。 色々試行錯誤した挙句わかったのは、 css内とmanifest.webapp(manifest.json)内で 指定しているpngが同じだと、 webpackがpng fileをhash名化する時にバッティングするようで、 生成後のpng fileはviewerで見えないし、 fileコマンドで見ると「data」とか出るし。 cssでのpng fileを../../../../../../webpack/logo-jhipster.png といったようにしてmanifest.webpackと別物にすると、うまく行きました。 ./src/...以下でなくてもいいんですね!

Service Worker on JHipster

| Comments

JHipsterでService WorkerでWeb Pushを、 と思っていて、Angularだから、 前やったように app.modules.tsServiceWorkerModule.register('./service-worker.js',...);してから@angular/service-workerSwPushをinjectして、 って思ってたんですが、違うんですね。 JHipsterではworkboxでService Worker使うようになってるんですね予め。 確かにindex.htmlservice-worker.jsを有効にして挙動を見てると、 cachingは綺麗に行っている様子です。 えーでも調べてみると、workboxってWeb Pushはないじゃーないですかー。

特に私の知っている範囲ではworkboxはプッシュ通知のロジックを作ってくれないので、そこは自分で書いてやる必要があります。 ServiceWorkerを簡単に書けるworkbox-swの使い方

しかも、自分のcodeとmergeする、 即ちworkboxPlugin(...)swSrc:を追加すると、 generateSWが使えず自分で書かないといけないの!? ですか? いやー、jhipsterで生成されるservice-worker.jsとか見ると、 色んなfilesにhash値?が付与されているから、 これを自分で作るというのはあり得ないでしょー。 どーしたらいーのー?!

と、途方に暮れました。

workboxを捨ててAngularのSwPushにする? いやー、でもjhipsterでのbuildではng実行されないので、 いくら.angular-cli.json"serviceWorker": trueと書いても効かないんですよー。 Angularのservice workerは生成されないわけですね。

jhipsterで生成されたbuild/www/service-worker.jsを見ていると、 上部のコメントに、

1
2
3
4
 * The rest of the code is auto-generated. Please don't update this file
 * directly; instead, make changes to your Workbox build configuration
 * and re-run your build process.
 * See https://goo.gl/2aRDsh

とあります。言われるままにそのURLを見てみると、 importScriptsというoptionがあることがわかりました。 これを指定するとどうなるのかなー、試してみます。 指定する場所は、webpackでworkboxのservice worker生成をやっているので、 webpack/webpack.prod.jsになります。 それの最下段、new WorkboxPlugin.GenerateSW({...})の中に importScripts: ['push-notifications.js']と書いてみると、 自動生成されたbuild/www/service-worker.js中に、 importScripts("push-notifications.js",...)と出ます。 なるほど。 ではこのpush-notifications.jsというのを作ってwww root folderに置き、 そこにpush notificationのlogicを書いておけばいいんじゃーん。 でも、基本的には全てのfile名はhash化?されてしまいます。 どうやって名前をhash化されないようにするの?! favicon.ico等の例から手探りで探し当てました。 webpack/webpack.common.jsnew CopyWebpackPlugin([..])に 書いておけばいーんですね。なるほど。

結局要するにworkboxのimportScriptsを使うということで、具体的には、

  1. src/main/webapp/push-notification.jsにWeb Push通知時の処理addEventListener('push', function(event) {...});と通知をclickした時の処理addEventListener('notificationclick', function(event) {...});を書いておく
  2. webpack/webpack.prod.jsnew WorkboxPlugin.GenerateSW({...})importScripts: ['push-notifications.js']を書く
  3. webpack/webpack.common.jsnew CopyWebpackPlugin([..]){ from: './src/main/webapp/push-notifications.js', to: 'push-notifications.js' },を書く

で目出度くJHipster application上でのWeb Push通知が出来ました。

尚、通知の許可を求める処理やendpoint、auth、publicKeyを求める処理では、 AngularのSwPushを使うことが出来ます。 JavaScriptでゴリゴリ書かなくてもthis.swPush.requestSubscription()だけで済むのでラクです。

Daemonizing Jhipster Application

| Comments

JHipsterのapplicationで、 OS(Ubuntu 18.04)起動時にapplicationもdaemonとして自動起動するようにするには。

きっとSpring Bootでdaemonizeする方法を探ればいいと思って、 61. Installing Spring Boot Applicationsにあるように、

1
2
3
bootJar {
	launchScript()
}

でもこれ、Spring Boot 2での話で、 こちとらまだJHipster 4.14.4、Spring Bootは1.5です。 そんなものはない、と当然失敗します。 なのでもうちょっと古い記事を探しました。

spring bootアプリの起動スクリプトを作るを見て、

1
2
3
4
5
apply plugin: 'spring-boot'

springBoot {
    executable = true
}

としたんですが、plugin 'spring-boot'はないと言われ、 executable = truebuild.gradleに既に書いてありました。

そもそもそんなことしなくても、 Using in Production にあるように、

1
$ gradle bootRepackage -Pprod

でexecutable war作れるんですね。 で、それを/etc/init.d/にsymlinkすればいいだけという。 -Pprodを付けないとdevelopment versionになってしまいます。 あとは、update-rc.d appname defaultsで登録すれば良いです。

Mock Web Server by WireMock on JHipster

| Comments

外部web siteのresponseのtestにWireMockを使ったのでメモ。

参考: WireMockを使って通信に関するテストをやってみよう

使う時はbuild.gradleで、

1
testCompile 'com.github.tomakehurst:wiremock-standalone:2.15.0'

して(wiremock-standaloneでないと、 jetty系のClassNotFoundExceptionに見舞われた)から、 test classの方で、

1
2
3
4
5
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
    :
    @Rule
    public WireMockRule wireMockRule = new WireMockRule(8089);

とし、各test method内で、

1
2
3
4
5
6
7
        stubFor(post(urlPathEqualTo("/any_path"))
                .withRequestBody(matching(".*arr_stn=%96%BC%8C%C3%89%AE.*"))
                .withRequestBody(matching(".*dep_stn=%93%8C%8B%9E.*"))
                .withRequestBody(matching(".*train=1.*"))
                .willReturn(aResponse()
                            .withStatus(200)
                            .withBodyFile("for_test.html")));

とstatic importした com.github.tomakehurst.wiremock.client.WireMock.stubForを使えば良い。 matching rule詳細は公式ページを。 post body requestに対して.withQueryParam("...", matching("..."))は効かなかった。 また、matchingなので全体にmatchingさせる必要があることに注意。 find系はなし。

test対象classでのqueryがここでの指定に合わなければ、404がthrowされる。

上記のように、test用に既に用意してあるhtml fileを.withBodyFile("...")で指定でき、 実体はsrc/test/resources/__files/に置く。 .withBodyFile(...)は公式Docに無く、JavaDoc APIも無いので、 Github上のsource codeを眺めて見つけた。

こうしたmappingをsrc/test/resources/mappings/以下にanyName.jsonを置いて、 requestとresponseを指定できるのだが、 それだと「こういうrequest bodyの時はこのようなresponse、 こっちの時はこれこれのresponse」というように、 複数の条件を書くことが出来なかった (書いてみると、最後のrequest/response条件しか効かず。 ならばと複数fileに分けてみたら、 file名alphabeticalで最後のrequest/responseしか効かず)。 なので、Javaでstub条件書いて各test method内に書くしか無い?

これはSpringの話だが、 testの時だけlocalhost:8089を見るようにするには、 〜.config.ApplicationProperties classにfield定義、setter/getterのaccessorを付け、src/main/resources/config/application.ymlapplication:以下に通常時の値を、src/test/resources/config/application.ymlapplication:以下にtest時の値(この場合はhttp://localhost:8089/any_path)を記述。 実際に使うには、

1
2
    @Autowired
    private ApplicationProperties properties;

とDIさせて、properties.get〜()と普通にgetterでget。

AssertJ、method chainで書けるので、 static importがassertThatだけで済んでいいですね。

これはJHipsterの話?(Springかなぁ?)で、 Test Class内で@Autowiredしてるservice classのmockが必要になったら、 特にbuild.gradeに記述せずともimport org.mockito.Mock;して mockitoの@Mockが使えるんですね。 そう言えば、元々import org.mockito.MockitoAnnotations;されてますね。

Unit Test on Jhipster

| Comments

JHipsterでのunit testでいくつかハマったのでメモ。

  • unit test実行はgradle test

  • 単一test classを指定するにはgradle test --tests FQDN.class.name

  • test class内のlog.debug(...)を出力するには、build.gradletest.testLogging.showStandardStreams = trueを記述し、またsrc/test/resources/application.ymlに以下を記述(src/main/resources/application-dev.ymlには記述あり)

    1
    2
    3
    4
    5
    
    logging:
        level:
            ROOT: DEBUG
            bz.mydns.walt.canmatch: DEBUG
            io.github.jhipster: DEBUG
    

  • test classにおいてRESTで認証したuserを表したい時には@WithMockUser(import org.springframework.security.test.context.support.WithMockUser;)

  • testされる側のREST methodでは、java.security.Principalを勝手に引数に加えることでlogin userを取得出来、それをtestする場合にはtest classのMockMvcrestUserInfoMockMvc.perform(post("/api/user-infos")...)などとする時にorg.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.userを使って.user("foo")とすると、認証された"foo"というuserでのaccessをmock出来るが、org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication()というstatic methodでjava.security.Principalが取れ、それに.getName()で名前を取得可能。その方がspringっぽいのかな。test class側では、認証accessが必要なmethodに上記の@WithMockUserをつければ良い。@WithMockUser(username="foo", password="pw", roles={"USER"})などとすれば色々指定可能だが基本的には不要

参照