Goalist Developers Blog

Deploying an Angular app to GitHub Pages

Being a programmer I can't stop myself greeting you with Hello Word so

f:id:vivek081166:20180620155225p:plain

Do you want to tell the world about all the great things you are building?
Well, GitHub Pages might help you to do that.

In this blog, I would like to share the process of bringing your Angular app from local to the internet

http://localhost:4200 ==> https://<username>.github.io/<repository-name>

Let's walk through the following steps to deploy our test-app on GitHub Pages

f:id:vivek081166:20180620142950p:plain

So let's get started...

Step 1 : Build your Angular app

With Angular CLI, using the following command; build your Angular app

ng build

After building your app all we need is the contents of the dist folder

f:id:vivek081166:20180620130056p:plain

Step 2 : Create a repository

For the purpose of demoing your app, it is good idea to have a separate repository.
So, let's create one for our test-app and name it as test-app-demo

f:id:vivek081166:20180620143918p:plain

Step 3 : Initialize repository for file upload

The simplest way to initialize files upload via GitHub will be to add .gitignore to your repository.

f:id:vivek081166:20180620155443p:plain

and commit your file

f:id:vivek081166:20180620155529p:plain

Step 4 : Upload your build files

Having done the previous step you can now upload the build files via GitHub

Click the Upload Files button on master branch

f:id:vivek081166:20180620155853p:plain

Drag your build files here or simply click choose your files

f:id:vivek081166:20180620155938p:plain

Go to the project source folder and choose dist folder

f:id:vivek081166:20180620160410p:plain

f:id:vivek081166:20180620160656p:plain

Select all the build content and upload

f:id:vivek081166:20180620161209p:plain

Commit your uploaded file to mater-branch

f:id:vivek081166:20180620161629p:plain

Step 5 : Update the base URL

Since we uploaded build files into the separate repository, we need to update the base URL to let angular know about the root directory

Update index.html file

f:id:vivek081166:20180620162455p:plain

Change base tag from
<base href="/">
to the name of the repository
<base href="/test-app-demo/">

f:id:vivek081166:20180620162627p:plain

f:id:vivek081166:20180620162704p:plain

Step 6 : Set up the GitHub Pages

This is the final step and we are almost done!

Navigate to the Settings tab of the repository for setting up a GitHub Page for our Angular app

Settings option is only visible to the owner of the repository

f:id:vivek081166:20180620162851p:plain

scroll down a little and you are almost there...

f:id:vivek081166:20180620163319p:plain

f:id:vivek081166:20180620163456p:plain

Select master-branch as the source of GitHub Pages

f:id:vivek081166:20180620163835p:plain

Save the changes and you are done!!

f:id:vivek081166:20180620163958p:plain

しばらくお待ちください。。。
Wait while GitHub sets up everything for you and holla!! your app is live

f:id:vivek081166:20180620164139p:plain

Your app will be live on the web address shown in the GitHub Pages' section. https://<username>.github.io/<repository-name>
Yes!! we did it.

f:id:vivek081166:20180620164542p:plain

Thank you very much for reading all to the end.

I'll be coming back soon with another such topic. Till then
Happy Learning :)

VPCピアリングして別AWSアカウントのセキュリティグループを使う

こんにちは
Webの開発者だけどインフラも知りたいお年頃のイイオです

やりたいこと

  • とあるEC2たちを別AWSアカウントに移し、そっち側からssh接続したい
  • このEC2たちは生まれては消えるさだめなので、セキュリティグループのソースにIPアドレスとかいちいち設定できない
  • というわけでセキュリティグループのソースに別AWSアカウントのセキュリティグループ指定したい

f:id:y-iio:20180614181210p:plain

手順

いいからVPCピアリングだ!

docs.aws.amazon.com

0. 前提

アカウントA

  • VPC CIDR 10.0.0.0/16
  • サブネット 10.0.10.0/24, 10.0.20.0/24

アカウントB

  • VPC CIDR 172.30.0.0/16
  • サブネット 172.30.10.0/24, 172.30.20.0/2

VPCのCIDRが被っているとピアリングできないよ
最初てきとうにVPC切ってしまってたいへん後悔したよ
このあたりをちゃんとべんきょうしてからやるべきだったよ

dev.classmethod.jp

リージョンも同じじゃないといろんな制限をくらうよ

1. アカウントAでVPCピアリングの申請

VPC > ピアリング接続 > ピアリング接続の作成

f:id:y-iio:20180614182341p:plain

AWSのアカウントIDはアカウント設定画面からみれる

2. アカウントBでVPCピアリングの許可

VPC > ピアリング接続

f:id:y-iio:20180614182806p:plain

f:id:y-iio:20180614182910p:plain

f:id:y-iio:20180614182932p:plain

3. アカウントBでルートテーブルを指定

VPC > ルートテーブル

送信先は相手のVPC全体にもできるし、もっと小さくサブネットとかIP単位にも絞れる、ゴイス〜

f:id:y-iio:20180614183259p:plain

4. アカウントAでルートテーブルを指定

VPC > ルートテーブル

こちらでもルートを追加、これ忘れてて詰まってたことは内緒

f:id:y-iio:20180614183316p:plain

5. アカウントAのセキュリティグループの設定

EC2 > セキュリティグループ

向こうのアカウントのセキュリティグループを入力できるようになっている!

f:id:y-iio:20180614184215p:plain

アカウントBのサーバーから接続確認だ〜〜〜

ssh ec2-user@ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com(パブリックDNS)

沈黙

ssh ec2-user@54.xxx.xxx.xxx(パブリックIP)

繋がらないよ〜〜〜〜

はい

qiita.com

www.skyarch.net

セキュリティグループでセキュリティグループを許可する(セキュリティグループのソースにセキュリティグループ指定する)方法は、プライベートネットワーク内でのみ有効…!!!

VPC Aの中でディグる

dig ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.62.rc1.55.amzn1 <<>> ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37867
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com. IN A

;; ANSWER SECTION:
ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com. 20 IN A 10.0.10.123

VPC Aの外でディグる

dig ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com

; <<>> DiG 9.10.6 <<>> ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8580
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 13, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com. IN A

;; ANSWER SECTION:
ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com. 589434 IN A 54.xxx.xxx.xxx

ピアリングしたVPC Bの中でディグる

dig ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.62.rc1.57.amzn1 <<>> ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 34546
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com. IN A

;; ANSWER SECTION:
ec2-54-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com. 60 IN A 54.xxx.xxx.xxx

今まで完全に同じVPC内でしか使ったことなかったから
接続先にパブリックDNSやパブリックIP指定しても、プライペートIPとして解決されていてたけど
VPCピアリングだとその解決できないから、プライペートIPを直指定しないとダメだったんだね、ハム太郎!へけ!

というわけで
アカウントBのサーバーから再度接続確認だ〜〜〜

ssh ec2-user@10.0.10.123

これでよろし〜〜〜なんだか全てにおいて己の知識不足って感じだ!へけ!

Play Frameworkで画像・アセットをユーザーに届ける

Play Frameworkが非常に便利ですので、Apiサーバーとして使ってます。この度、自分のサーバーに持ってる画像などなアセットをそのまま返したい場合(例え、アンギュラーのフロントで表示したいなど)にどうすればいいかを説明します。 それぞれの状況に合わせて、二つの方法を説明します。

とりあえず、…image.jpgに画像を出したい!

つまり、誰でもそのurlにいくとその画像がみれるような場合です。 この方法は割と簡単です。

conf/routesのファイルに

GET     /assets/*file               controllers.Assets.versioned(path="/public/images/", file)

これで、/public/images/のディレクトリーをベースにして、「ファイル」というパス変数に当てはまるファイルをそのまま出せます。

www.example.com/assets/cats/myCat.png/public/images/cats/myCat.pngにあるファイルを出します。(なかったら、404エラーが出ます)

ちなみに、Idea使ってる方々はこのようなdeprecatedという知らせを見ることもあると思いますが、Ideaのバグらしいです。(link: https://github.com/playframework/playframework/issues/7521)

このように、Angularのクライアントで、<img src="www.example.com/assets/cats/myCat.png" />

このイメージのアクセスを制限したいですが…

アクセス制限、何かのトラッキング・編集などのことがやりたい場合もあると思います。その場合は直接routesから返したらできなくなるかと思いますが、別な方法もあります!

では、routesにこう追加しましょう

 GET       /getImage                controllers.HomeController.getImage

getImageの中でバリデーションやアナリティクスなど、でもできます。 欲しいイメージや、アクセス権限の情報がGET変数で送られてるとしますと、FormFactoryとDynamicForm を使って、コントローラーからその変数のアクセスができます。

public class MyController extends Controller {

@Inject
private FormFactory formFactory;

public Result getImage() {
...
}

}

まずは、FormFactoryをInjectionで使えるようになる。そして、getImageの書き方は...

DynamicForm dynamicForm = formFactory.form.bindFromRequest();
// 適切なログインチェックを行う
if(!isLoggedIn(dynamicForm)) {
    // もしユーザーがログインしてない場合には、対応する。
        return ....; //アクセス制限する。
}

まずは、バリデーションができる、そしてアクセス制限したい場合にはふさわしい風に行える。 その後、ユーザーが頼んでるイメージの正式なパスを作成し、Resultを返す。

// ユーザーが送ってるファイル名的なパスと実際のサーバーのパスが違うのはず。
String path = getRealImagePath(dynamicForm.get("")); 
java.nio.file.Path deliveryPath = Paths.get(path);
// これはPlayFrameworkに存在する、ファイルをだす方法:
return new Result(
          new ResponseHeader(200, Collections.emptyMap()),
          new HttpEntity.Streamed(FileIO.fromPath(deliveryPath), Optional.empty(), Optional.of("image/png"))
);

こんな風に、画像や他のアセットのアクセスを制限することもできます。

App Analyticsについてまとめてみた

こんちは、渡部です。
久しぶりの投稿です。
今回はiOSアプリのAnalyticsについてです。
「GoogleAnalytics仕込むの面倒!」という俺のためのiOSエンジニアのための記事です。
記事を書いてる途中でiTunesConnectがアップデートされて焦りましたw

f:id:watabe1028:20180605204659p:plain

目次

App Analyticsとは?

自分が管理するアプリのApp Storeページが
・どのくらい見られているか(PV)
・どのサイトから遷移してきたか(リファラー)
・そのうちどれくらいダウンロードされたか(ダウンロード率)
・ダウンロードした翌日以降も使ってくれている人の割合(使用率)
などが見れる解析サービスです。
Apple純正なので、特別なSDKを導入する必要がないのが面倒臭がりにはすごくいい!
計測データに含まれるのは iOS 8以上のデバイスのみらしいです。
閲覧にはAdmin, Finance, もしくは Sales権限が必要になります。

 

どんなデータが見られるのか

App Analyticsは、「概要」「メトリックス」「ソース」「使用率」の4つの画面から構成されてます。
 

1. 概要

全期間および過去30日間の各データを閲覧できます。それぞれの項目を解説していきやす。

App Store閲覧数
アプリのApp Storeページが閲覧された回数。

Appユニット数
アプリがApp Storeページからダウンロードされた回数。
アップデートや、同じApple IDからの再ダウンロード数はカウントされない。

売上
アプリの売上。有料アプリ, app bundles(複数のアプリをまとめて売れるやつ良くわからない), In- App Purchasesを合わせた金額。

セッション数(オプトインのみ)
セッション数とは、アプリが少なくとも2秒間フォアグラウンドにされた回数のこと。
その後、アプリがバックグラウンドになって、再び使用されると別のセッションとしてカウントされる。
診断データおよび利用情報を App 開発者と共有することに同意したユーザのデータのみ。

アクティブなデバイス数(オプトインのみ)
指定した期間内に少なくとも1セッションとしてカウントされたデバイスの数。
診断データおよび利用情報を App 開発者と共有することに同意したユーザのデータのみ。

テリトリー別の集計
各国ごとの閲覧数、ユニット数、売上、インストール数、アクティブなデバイス数が見れる。

使用率(オプトインのみ)
アプリを初期インストールし、その後 App を使用したユーザの割合。
1日目 7日目 14日目 21日目 28日目の使用率が見られる。

プラットフォーム別の集計
プラットフォームごと(iPhone/iPad/iPod)の閲覧数、ユニット数、売上、インストール数、アクティブなデバイス数の割合が見れる。

 
※オプトインとは
加入や参加、許諾、承認などの意思を相手方に明示することらしいです。 e-words.jp

 

2. メトリックス

1日ごとの各データをグラフにして表示してくれます。
・App Store 閲覧数
・App ユニット数
・App 内課金数
・売上
・有料ユーザ
・インストール数(オプトインのみ)
・セッション数(オプトインのみ)
・アクティブなデバイス数(オプトインのみ)
・過去30日のアクティブなデバイス(オプトインのみ)
・クラッシュ数(オプトインのみ)
それぞれのデータを同時に表示して、比較することもできます。

 

3. ソース

「トップWebサイト」「トップキャンペーン」の2つから構成されます。

トップWebサイト
どのサイトから自分のアプリのApp Storeページヘ流入しているかがわかります。
・流入元ドメイン
・閲覧数
・ユニット数
・売上
・セッション数

トップキャンペーン
キャンペーンリンクを作成することで、どのキャンペーンからの流入が多いかを計測できます。

 

4. 使用率

指定した日にアプリをインストールして、翌日以降にアプリを使用したユーザの割合を詳しく確認できます。

 
 

おまけ

こちらは弊社アプリ「HRog」の実際のデータです。
毎週記事を更新しているのでパターンがあります。
(数値は見せられません)
すでにパターンが見て取れますが何をどうして良いやら。。。
f:id:watabe1028:20180608112231p:plain

 

まとめ

App Analytics、SDKの導入が必要ないのでとても助かります。
GoogleAnalytics、AppsFlyer、App Analyticsで数値を比べてみたいですね(自分ではやらないけど)。
App Annieのデータとの関係性も気になります。
 
また、ファネル分析やコホート分析もできます。

コホート分析
時間の経過に伴うユーザー行動の変化を可視化する  
ファネル分析
入口ページから目的達成までの離脱率を把握する

実はグロースハッカーになりたいのでこの辺も勉強してブログに書けたらな、と思ってます。(書くとは言ってない)

IaC by Terraform ~DAY4: Terraform Commands (3)~

どうも,エンジニアのナカノです.

少し前までは春うららな感じでしたが,最近は雨の日が多くなってきましたね.

また,もう少ししたら梅雨の時期に入るため,若干気が滅入ります.

歩きづらかったり,洗濯物を外に干し難いですし,癖っ毛なので髪型がヤバくなります.

一方で,晴れの日は暑すぎて体力が奪われそうです.これでは夏の来訪に恐々とします.

さて,今回も前回の記事の内容の続きとなります.

前回は場合によって役立つTerraformのコマンドに関する内容でした.

今回の内容は,terraform.tfstateの管理/運用コマンド(1)に関してです.

目 次
  • `terraform.tfstate`の管理/運用コマンド (1)
    • state
      • list
      • mv
      • pull
      • push
      • rm
      • show
    • force-unlock
  • 最後に

terraform.tfstateの管理コマンド (1)

state

このコマンドは,terraform.tfstateファイルを管理/運用するためのものです.

直接な修正という手もありますが,このコマンドを使った方が遥かに安全です.

サブコマンドとしてlist, mv, pull, push, rm, showがあります.

以下では,これらのサブコマンドの機能についてご紹介致します.

  

list

terraform.tfstateの内容をリストアップしたい時は,このlistを使うとよいでしょう.

特に指定なしでコマンドを実行すると,下記の様な挙動を起こします:

  • バックエンド設定を利用していない:ローカルにあるterraform.tfstateの内容をリストアップする.
  • バックエンド設定を利用している:リモートに保管されているterraform.tfstateの内容をリストアップする.

前者ですが,terraform.tfstateの場所がデフォルトではない場合は-state=pathというオプションを使う必要があります.

www.terraform.io

  

mv

このコマンドを使うと,下記の様なことを行うことが出来ます:

  • Terraformによる管理の対象リソースの設定名を変更する.
  • リソース設定をモジュール設定へ移行する.
  • モジュール設定を別のモジュール設定へ移行する.
  • モジュール設定を別のterraform.tfstateへ移行する.

これらは,あくまでterraform .tfstateのリファクタリングに関するものです.

そのため,モジュールやリソースなどの設定ファイルの修正は別途行う必要があります.

www.terraform.io

  

pull

これを実行すると,以下の様な挙動を起こします:

  • terraform.tfstateがリモートで管理されている:リモートのterraform.tfstateの内容をターミナル上にアウトプットする.
  • terraform.tfstateがローカルで管理されている:ローカルのterraform.tfstateの内容をターミナル上にアウトプットする.

このコマンドは,特にリモートで管理されているterraform.tfstateに対して効果を発揮するかもしれません.

しかし,S3のバックエンド設定を使っている方にとってはあまり使われることのないコマンドかもしれません.

www.terraform.io

  

push

先ほどのコマンドに対して,このコマンドはローカルのterraform.tfstateをリモートにアップロードするのに使われます.

こちらも,S3のバックエンド設定を使っている方にとってはあまり使われることのないコマンドかもしれません.

www.terraform.io

  

rm

これを使うと,Terraformの管理対象外にしたいリソースやモジュールをterraform.tfstateから除外させることが出来ます.

あくまでTerraformの管理対象に関するコマンドですので,これを実行したからといってリソースの実態が削除されるわけではありません.

このコマンドは,場面によっては使いどころがあるかもしれません.知っておいてもよさそうなものかと思います.

www.terraform.io

  

show

これを用いると,リソースやモジュールの設定の詳細を見ることが出来ます.

コマンド実行の結果を考えると,これはterraform showのコマンドと類似しています.

www.terraform.io

  

force-unlock

このコマンドの紹介の前に,まずterraform.tfstateロックについて説明します.

Terraformを複数人で利用したい場合ですが,大体はterraform.stateをリモートで保管します.

保管の点ではこれはよいのですが,複数人による同時のapplyの実行を考えると恐ろしいですよね.

ロックとはこのコマンドの同時実行を防いでくれるものです.バックエンド設定でロックの設定を追記することが出来ます.

さて,force-unlockについてですが,このコマンドを実行するとロックが無効になります.

これの使いどころですが,applyなどを強制終了させてしまいロックが有効になっている場合に役に立ちます.

www.terraform.io

  

最後に

今回はterraform.tfstateの管理/運用コマンド(1)を紹介させて頂きました.

どれも,チームでTerraformを利用する場合に威力を発揮する様なものばかりです.

そのため,個人でTerraformを使う場合ではコマンドの良さを感じ難いかも知れません.

しかし,知っておいて損はないものばかりですので,この際に覚えて頂けると幸いです.

次回は,terraform.tfstateの管理/運用コマンド(2)を紹介致します.

それでは,今回はこの辺りで筆を置かせて頂きます.

次回に乞うご期待下さい.

Gradle利用のSeaser2(S2JDBC)をJava8にする

Java7でS2JDBCを使っているプロジェクトをJava8にしたので手順を残します。
プロジェクト自体はGradleで管理されています。
EOLの問題があるけど、諸事情によりS2JDBCをJava8に対応させないといけない場合に役に立てばいいなぁと思います。
やるべきこと(確認ポイント)は以下です。

  • Gradleの設定をJava8でコンパイルするように変更する
  • s2-frameworkのjavassistだけを新しいものに置き換える
  • javax.transaction-apiが必要な場合は入れる
  • MySQLはバージョン5系を使う

1. Gradleの設定をJava8でコンパイルするように変更する

これはそんなに難しくないと思います、build.gradleの対象ヶ所を書き換えます。

sourceCompatibility = 1.8
targetCompatibility = 1.8

2. s2-frameworkのjavassistだけを新しいものに置き換える

今回の作業で一番はまったのはここでした。 dependenciesで以下のように設定しても古いjavassistが含まれてしまいます。

 // https://mvnrepository.com/artifact/org.seasar.container/s2-framework
 compile('org.seasar.container:s2-framework:2.4.44') {
   exclude module: 'javassist' 
 }
 // https://mvnrepository.com/artifact/org.javassist/javassist 
 compile group: 'org.javassist', name: 'javassist', version: '3.20.0-GA'   

 // https://mvnrepository.com/artifact/org.seasar.container/s2-tiger
 compile group: 'org.seasar.container', name: 's2-tiger', version: '2.4.44'   

 // https://mvnrepository.com/artifact/org.seasar.container/s2-extension
 compile group: 'org.seasar.container', name: 's2-extension', version: '2.4.44'

s2-frameworkでexcludeしてもs2-tigerやs2-extensionの部分で古いjavassistを取得してしまいます。
上記の設定からs2-framework以外を省いてgradle dependenciesで確認すると古いjavassistが含まれないので、excludeは機能していることが確認できます。 解決策としては以下のようにexcludeではなく、configurationsで全体から外してやればOKです。

configurations {
    all*.exclude group: 'jboss', module: 'javassist'
}

dependencies {
    // https://mvnrepository.com/artifact/org.seasar.container/s2-framework
    compile group: 'org.seasar.container', name: 's2-framework', version: '2.4.44'
    // https://mvnrepository.com/artifact/org.javassist/javassist 
    compile group: 'org.javassist', name: 'javassist', version: '3.20.0-GA'
    // https://mvnrepository.com/artifact/org.seasar.container/s2-tiger
    compile group: 'org.seasar.container', name: 's2-tiger', version: '2.4.44'
    // https://mvnrepository.com/artifact/org.seasar.container/s2-extension
    compile group: 'org.seasar.container', name: 's2-extension', version: '2.4.44'
    // https://mvnrepository.com/artifact/javax.persistence/javax.persistence-api
    compile group: 'javax.persistence', name: 'javax.persistence-api', version: '2.2'
}

3. javax.transaction-apiが必要な場合は入れる

ここまでを実行して以下のエラーで怒られた場合はjavax.transaction-apiが足らないので入れましょう。

Caused by: java.lang.ClassNotFoundException: javax.transaction.UserTransaction
    // https://mvnrepository.com/artifact/javax.transaction/javax.transaction-api
    compile group: 'javax.transaction', name: 'javax.transaction-api', version: '1.3'

4. MySQLはバージョン5系を使う

Java8に上げるついでにMySQLコネクタのバージョンを6にしてみたのですが、エラーで怒られました。ドライバ名が変わったのが原因だったので、あきらめて5系を使えば大丈夫でした。
たぶんS2JDBCのdialectの設定が新しいドライバ名に対応していません。 ドライバ名について詳しくはこちら。
datameer.zendesk.com

まとめ

今回は既存のバッチプログラムをマルチスレッド化するために、どうしてもJava8が使いたかったのでSeaser2(S2JDBC)をJava8に対応させました。

JSでファイルダウンロードを実行したい場合はどうしたらいい?

一般的に、ファイルダウンロードのために

<a href="https://example.com/my_file.txt" download>click</a>

的なコードで行ってると思いますが、たまにリンクが事前に分からない状態であり、ボタンをクリックした後でリンクを作成してからダウンロードを実行する時もあります。その場合、サーバーが返してくれるリンクのダウンロード実行をjavascriptで行って欲しい時もあるかと思います。

普通のやり方

ほとんどの時、

document.location.assign(url);

window.open(url, '_self');

と書いて、実行できるかと思います。ただし、ファイルの種類によってブラウザーに怒られる可能性もあります。 この間まさに、

f:id:c-pattamada:20180517113207p:plain

と怒られました。 つまり、「本当のファイルがzipなのに、なんでtext扱いしてるの?!」と。 ただのワーニングなのです。でも解決があるかどうか、を調べて見ました。

まずは、

window.open(url, '_blank')

はどうでしょうか?

f:id:c-pattamada:20180517113225p:plain

確かにな、サイトが勝手に何かをダウンロードして欲しくないよね、と思いました。javascriptはそういうことのためにもいっぱい使われてますね。納得。

ですが、一方にあるポップアップブロッカーの存在すら気づいてないお客さんもいるらしい。ダウンロード期待してるのに、何も起こらなくて困ってて問い合わせが頻繁にくるというのが聞きましたので、このままじゃダメだねと思いました。

ということで、ポップアップブロッカーのCMを作りました。 違います。

元のワーニングありの状態に戻すか、別な方法を探すかという二つの選択肢がありました。

リンク作成後->更新してクリック。

まずはhtmlのリンクを作成します。

<a download id="downloadLink" #downloadLink style="display:none;"></a>

‘download’が書いてあるので、ブラウザーがリンクされてるファイルを開こうとせずに、ダウンロードする。

Angularの〇〇.component.tsの中からhtmlをアクセスする方法はこんな感じです:

@ViewChild('downloadLink') downloadLink: ElementRef;

では、このhtmlをどう編集すればいいでしょうか?

this.downloadService.getDownloadUrl(myVariables).subscribe(
       myUrl => { //サーバーからurlをもらって、
                 // 上記のリンクを更新して
        this.downloadLink.nativeElement.href = myUrl; 
        this.downloadLink.nativeElement.click(); 
                  //終わったら、クリックできなくする。
        this.downloadLink.nativeElement.removeAttribute('href');
        }
);

をやって見たら何も文句言わずにダウンロードできました。

結論

こういうものでしょうかな。ワーニング付けの一つめの方法と最後の方法のどっちのほうが好み次第だと思います。 個人的にワーニングをなくそうと頑張ってるつもりながら、2番目の方法は本来ユーザーがするはずのリンククリックをプログラムで実行してるのでちょっとづるいと思ってるところもありました。