S3だけでブログを作る

#S3だけでブログを作ります

## サーバーレスでブログを作るメリット
特にブログのアクセス数が少ない場合、インスタンスを一か月間丸々借りることが割に合わないと感じます。月間アクセス100でインスタンス代が800円なら1アクセス8円です。
また、きちんと監視自動化をしていなければプロセスが落ちることもあります。業務中にNodeが落ちていることに気づいた場合、お昼休みまで対応することができません。

## 既存の方法のデメリット

### GAEを使うデメリット
アクセス数が少ない場合、お客さんが来てくれるたびにスピンアップのオーバーヘッドがかかることになります。また、開発言語がGo/Python/PHP/Javaに限られる点も考え物です。HTMLのテンプレートをReactかAngularで書きたい場合、Cloud Functionと連携することになりますが、少々スピードに難が出ます。

### AWS Lambdaを使うデメリット
Cloud Front => API Gateway => Lambdaという方法もありますが、これもスピードに難があります。

## 今回提案する方法
そこで、編集者のブラウザ上で

 1. Reactを使ってHTMLをレンダリング
 2. S3のAPIでアップロード

という作戦でブログを作ってみました。

## 実装

### Cloud FrontとS3の準備
こちらの[クラスメソッドさんの記事](http://dev.classmethod.jp/cloud/aws/cloudfront-s3-origin-access-identity/)を参考にS3を設定します。

### CORSの設定
ブラウザから直にAPIをたたけるようにする必要があります。

```xml:cors.xml
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

```

### AccessKeyとSecretKeyの取得
AWSコンソールから -> IAM -> ユーザーの追加を選択しユーザーを追加します。

次にアクセス権限を設定します。必要な権限は作成したS3バケットの読み書きです。

```json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "***",
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::{{s3のバケット名}}",
                "arn:aws:s3:::{{s3のバケット名}}"
            ]
        }
    ]
}
```

最後に認証情報タブからアクセスキーの作成ボタンを押してAccessKeyとSecretKeyを取得します。

### ブラウザ用S3 APIクライアントを取得する
[AWSの公式](https://sdk.amazonaws.com/builder/js/)からS3のAPIクライアントを取得しローカルに保存します。

### 開発環境のセットアップ
Typescript + browserify + browserify-shim + gulp で作っていきたいと思います。

```json:package.json
{
  "name": "firebase-blog",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "browserify": "^13.1.1",
    "browserify-shim": "^3.8.12",
    "gulp": "^3.9.1",
    "gulp-typescript": "^3.1.2",
    "gulp-webserver": "^0.9.1",
    "typescript": "^2.0.6",
    "vinyl-source-stream": "^1.1.0"
  },
  "dependencies": {
    "@types/aws-sdk": "0.0.39",
    "@types/firebase": "^2.4.30",
    "@types/react": "^0.14.44",
    "@types/react-dom": "^0.14.18",
    "aws-sdk": "^2.6.15",
    "firebase": "^3.5.3",
    "react": "^15.3.2",
    "react-dom": "^15.3.2"
  },
  "browserify": {
    "transform": [
      "browserify-shim"
    ]
  },
  "browserify-shim": {
    "AWS": "global:AWS"
  }
}

```

```js:gulpfile.js
var gulp = require('gulp');
var ts = require('gulp-typescript');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var webserver = require('gulp-webserver');

gulp.task('compile', function () {
    return gulp.src(['src/**/*.ts', 'src/**/*.tsx'])
        .pipe(ts({
            noImplicitAny: true,
            jsx: "react",
            module: "commonjs",
            target: 'es6'
        }))
        .pipe(gulp.dest('build/compile'));
});

gulp.task('browserify', ['compile'], function () {
    return browserify({
        entries: ['build/compile/main.js']
    })
        .bundle()
        .pipe(source('index.js'))
        .pipe(gulp.dest('build/browserify'));
});

gulp.task('html', function () {
    return gulp.src(['src/**/*.html', 'src/**/*.js'])
        .pipe(gulp.dest('build/browserify'))
});

gulp.task('webserver', function () {
    return gulp.src('build/browserify')
        .pipe(webserver({
            livereload: true,
            open: true
        }));
});

gulp.task("watch", function () {
    gulp.watch('src/**/*.*', ['browserify' , 'html'])
});

gulp.task('default', ['watch', 'webserver']);
```

### 編集画面の作成

ブログの編集画面をこのような感じで作ります。
必要なFormの入力項目は

 - AccessKey
 - SecretKey
 - タイトル
 - 本文

Scriptタグで読み込まなければいけないソースは先ほどのS3 APIクライアントと後述するindex.jsです。

```html:index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>ブログ</h1>
<div>
    <input type="text" id="accessKey" placeholder="accessKey" /><br>
    <input type="text" id="accessSecret" placeholder="secretKey" /><br>
    <input type="text" id="title" placeholder="title" /><br>
    <textarea id="body" placeholder="本文"></textarea><br>
    <button id="submit">公開</button>
</div>

<script src="aws-sdk-2.6.15.min.js"></script>
<script src="index.js"></script>
</body>
</html>

```

### Reactでテンプレートの作成
これは公開するブログのBodyタグの中身です。
ここはひとまずこんな感じで作りました。

```tsx:template.tsx
import * as React from 'react';
import {Article} from './main'

export class Template extends React.Component<Article,{}> {
    render() {
        return (
            <div>
                <h2>{this.props.title}</h2>
                <p>{this.props.body}</p>
            </div>
        );
    }
}
```

### レンダリングとアップロードの処理
次に、編集画面の書き込みをS3に反映させる部分を書きます。
処理の流れはおおざっぱに

  1. submitボタンが押される
  2. Reactを使ってHTMLを作成する
  3. S3にアップロードする

です。

```ts:main.ts
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {Template} from './template';
import AWS = require('aws-sdk');

var submit = document.getElementById("submit");
submit.addEventListener("click", ()=> {

    var accessKey = document.getElementById("accessKey").value;
    var accessSecret = document.getElementById("accessSecret").value;
    var title = document.getElementById("title").value;
    var body = document.getElementById("body").value;

    var div = document.createElement('div');
    var prop = <Article>{
        title: title,
        body: body
    };
    var template = ReactDOM.render(React.createElement(Template, prop), div);

    var html = '<html><head><title>' + prop.title + '</title><meta charset="utf-8"></head><body>' + div.innerHTML + '</body></html>';

    var s3 = new AWS.S3({
        accessKeyId: accessKey,
        secretAccessKey: accessSecret,
        region: "ap-northeast-1"
    });
    var params = {
        Bucket: 'isyumi-blog',
        Key: 'index.html',
        Body: html,
        ContentType: "text/html; charset=UTF-8",
    };
    s3.putObject(params, () => console.log('done'));
});

export interface Article {
    title:string;
    body:string;
}
```

## 実行
コマンドラインから```gulp```を実行するとビルドされブラウザが立ち上がります。
そこに先ほど取得したAccessKeyとSecretKey、そして世に訴えたいことを書き込み公開ボタンを押すとCloud Frontから配信できます。

## 知見募集
 -  Cognito を使えば認証の部分をもう少し楽にできる気がしたのですが、調べたけどわかりませんでした。方法をご存知の方はぜひ教えてください。
 -  Firebaseを使えばGCSで同じことができる気がしたのですが、GCSにあげたファイルのWeb公開方法に詰まったので、もしやり方をご存知の方は教えてください。
 -  ReactではなくAngularで同じことをする方法も知りたいです。Angularの場合、window.document上に存在していないElementにテンプレートを適用する方法がわかりませんでした。

以上です。