オレオレHTTPSサーバを立てる

実際には証明書を買うとしても、開発時にはオレオレ証明書

$ openssl req -nodes -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 90
Generating a 2048 bit RSA private key
...........................+++
.........................+++
writing new private key to 'key.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
$ ls
cert.pem    key.pem

opensslを使って証明書を作ってますが、パラメータの「-nodes」が必要です。これを付けないとhttpsを上げた時に「error:0906A068:PEM routines:PEM_do_header:bad password」とエラーが出ちゃう。最後の90は有効期間で、伸ばしたければ365とか。途中のopensslとの対話ではパスフレーズを4文字以上で適当に入力する以外、リターンキーで省略OK。

var fs = require('fs');
var https = require('https');
var app = express();
var privateKey  = fs.readFileSync('key.pem');
var certificate = fs.readFileSync('cert.pem');
app.use(express.static('public'));
https.createServer({key: privateKey, cert: certificate}, app).listen(443 function() {
    process.setuid('masataka_k');
});

平民で実行するとデカいポート番号(8443とか)にしなければ「listen EACCES 0.0.0.0:443」というようにエラーで上がらない。ルートで実行。

$ sudo node server.js

ルートで上げっぱだとセキュリティ上ナニなので、process.setuid()をするってNodeクックブックに書いてあった。が、これでいいのかな?psするとこんな状態。最後によくわかってなくておまじない状態。本番あげるまでには識者を見つけねば。

$ ps -ax | grep node
 4020 ttys000    0:00.01 sudo node server.js
 4021 ttys000    0:00.33 node server.js
 4037 ttys001    0:00.00 grep node

Nodeクックブック

Nodeクックブック

SuperAgentとMulterでファイルアップロード

ファイルアップロードの機能を作るのに、二日ハマりました。前提としてReactで作ったFormから投げ、Express4で書いたサーバで受けるというところでの通信部分だけです。まずガチなところとしてサーバを作ります。

var express = require('express');
var multer  = require('multer');
var server = express();
var upload = multer({ dest: "./uploads/" });
server.post("/confirm", upload.single("contact_attachment"), function(req, res) {
    console.log(req.headers);
    console.log(req.params);
    console.log(req.body);
    console.log(req.file);
    res.sendStatus(200);
});
server.use(express.static('public'));
server.listen(process.env.PORT || 8080);

multerはマルチパートのリクエストボディを処理してくれるExpressのMiddlewareで、npmでサクッとインストール。いろんな書き方がありますが、uploadというインスタンスを作っておいてExpressのルーターチェーンに差し込みます。メソッド引数に並べて渡すだけでチェーンできるのは発見だった。違う書き方としては、以下もあります。

server.use("/confirm", upload.single("contact_attachment"));
server.post("/confirm", function(req, res) {
    console.log(req.body);
    res.sendStatus(200);
});

ルーティングの書き方は同じことでもいろんな書き方できるから、ググってる時に混乱多い。みんなマニアックなテクニック使いまくってること多くて慣れるまでルーティングがどうなってるかなかなか掴めないじゃないか!

一方で、ドハマりしたクライアント。迷走の先に気がついたのは、簡単なものとしてプレーンHTMLさえ書けばOKなんでまずそれ作って、後でもっと難しいことをやる時に、それぞれヘッダやパラメータやボディなどに何を書かれているかを見比べることでした。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
    <form method="post" enctype="multipart/form-data" action="/confirm">
        <div><input type="text" name="contact_name"></div>
        <div><input type="file" name="contact_attachment"></div>
        <div><button type="submit">UPLOAD</button></div>
    </form>
</body>
</html>

タグだけで書いたこれが動かないとしょうがない。だから一番初めにサーバがちゃんと動くことを確認すべきだった(私はやらなかったためサーバもクライアントも変更し続けて迷走が長引いた)。multerもたった数行のコードなのに、一発では動くものを書けなかったからねー。

クライアントはReactの中から、初めはjQueryの$.ajax()でやってたけど、どうにも動かなかった。普通にフォームPOSTするだけならすぐだったけど、ファイル添付されたmultipartができなかった。そんな中、ReactではDOMを直に触るの邪道っぽいこともあって通信にしか使わないので、ダメ元で通信専門のライブラリを持ってきました。SuperAgentです。

github.com

まあ、やることは$.ajax()と大差ない。

var React = require("react");
var SuperAgent = require("superagent");
var ContactForm = React.createClass({
    handleSubmit: function(event) {
        event.preventDefault();
        this.setState({
            valid_button: false
        });
  // var formData = new FormData() <==FormDataを使って迷走
        SuperAgent
            .post("/confirm")
            //.type("multipart/form-data") <==これをやるとboundaryが設定されなくなって迷走
            //.set({contact_name: this.state.contact_name}) <==set使って迷走
            .field("contact_name", this.state.contact_name)
            .field("contact_kana", this.state.contact_kana)
            .field("contact_email", this.state.contact_email)
            .field("contact_company", this.state.contact_company)
            .field("contact_message", this.state.contact_message)
            .attach("contact_attachment", this.state.contact_attachment)
            .end(function(err, res) {
                if(res.statusCode == 200) {
                    this.setState({
                        text_guidance: GUIDANCE_3,
                        style_form: {"display": "none"}
                    });
                } else {
                    this.setState({
                        valid_button: true,
                        text_guidance: GUIDANCE_4
                    });
                }
            }.bind(this));
        return false;
    },
   //以下、renderとか色々ごっそり省略
});

頭で"superagent"をrequireしてから、あとはfieldとattachでコツコツ積み上げます。multipartの場合は他のことをしちゃダメだった。より一般的なsetメソッドでパラメータを積んだりすると自動的にcontent-typeがjsonにされちゃう。また、typeで明示的にcontent-typeをmultipart/form-dataに設定すると続いてほしいboundaryのぐちゃぐちゃ文字列が設定されなくなるので死亡。

ライブラリのソース読んで理屈と癖がわかって、動くようになってみるとSuperAgentとMulterはなかなか良い。捗る。

ReactでinputのonChangeが効かなくて焦った話など

Reactでちょこちょこ書いていて、嵌ったところをメモ。ちなみにECMAScript6は、ExpressもReactもあまりメリットない割に、参考資料が少なくてどう書いていいのか悩む時もあるのでやめた。Babelでreact-presetだけ使ってます。

inputのonChangeが効かなくなった

var App = React.createClass({
    // getInitialStateとか、handleChangeとか、省略
    render: function() {
        return (
            <form>
                <input type="text" value={this.state.data} onChange={this.handleChange}/>
            </form>
        );
    }
});
ReactDom.render(<App/>, $("form")[0]);

上記のようなコードだったとします。コンポーネントがformタグを返すのに、ReactDom.renderの第二引数にformタグを入れてた。第二引数のDOM要素の内側をごっそり第一引数のコンポーネントで置き換える動作だったので、出来上がったHTMLはformタグが二重になってる状態です。こうなると、getInitialStateで設定した初期値は何事もなく描画されるけど、onChangeだけ効かないって不思議状態になる。HTMLの方をいじらないで済まそうとせずに、formを囲うdivを加えて修正しました。

大文字のHTMLタグはダメ

var App = React.createClass({
    render: function() {
        return (<P>ホゲホゲ</P>);
    }
});
ReactDom.render(<App/>, $("#app")[0]);

Pが大文字でクラッシュ。小文字に直せばOK。

タグにインラインのStyle指定

facebook.github.io

ドキュメントにちゃんと書いてありました。

Node.js 5.0へ

5.0!ほんの一年ぐらい前は0.10とか0.11とか名乗ってたのになあ。

www.publickey1.jp

運用モードでも全くないので、バシッと最新にしちゃいます。これまではnodejs.orgから落としてきたインストーラを使ったので「n」を入れて、そこからアップデートします。

$ sudo npm install -g n
$ n --latest
$ n latest

サクッと、結果は以下。

install : node-v5.0.0
mkdir : /usr/local/n/versions/node/5.0.0
fetch : https://nodejs.org/dist/v5.0.0/node-v5.0.0-darwin-x64.tar.gz
installed : v5.0.0

続いてnpmもアップデート。

$ sudo npm update -g npm
$ npm -v

結果は「3.3.12」ということです。

おいおい、babel周りがガラッと変わってるぞ

$ sudo npm update -g
$ npm update

何気なくGlobalもLocalも一切合切アップデートしちゃったんですよ。そうしたらもう、Babelifyが動かなくなってちょっとハマりました。ほんの数日前に書いたエントリが嘘八百。

babeljs.io

babel/babelify · GitHub に"> As of Babel 6.0.0 there are no plugins included by default. For babelify to be useful, you must also include some presets and/or plugins."って書かれてる通りBabelが変わって、Babelifyも当然変わる。プラグインのインストールとTranspileコマンドの書き換えを行います。

$ npm install --save-dev babel-preset-es2015 babel-preset-react
$ browserify App.jsx -o public/js/bundle.js -t [ babelify --presets [ es2015 react ] ]

-tスイッチの引数はスペースもちゃんと入れないと、[babelifyなんて知らん!と怒られます。パラメータのトークナイズがちょっと足りてないみたい。これで10/30に書いたのと同じことが再び出来るようになってます。babel-nodeもダメなんで、そちらはpackage.jsonの中にBabel設定を書きました。

// package.jsonの一部
{
  "devDependencies": {
    "babelify": "^7.2.0",
    "babel-preset-es2015": "^6.0.15",
    "babel-preset-react": "^6.0.15"
  },
  "babel": {
    "presets": [ "es2015" ]
  },
  "scripts": {
    "start_es6": "babel-node server.es6",
    "build_server": "babel server.es6 -o server.js",
    "bundle_client": "browserify App.jsx -o public/js/bundle.js -t [ babelify --presets [ es2015 react ] ]"
  }
}

WebStorm 11も製品版リリースしてた

リリースラッシュです。WebStrom も11.0になってました。EA版じゃなくなったし、そろそろ課金すっかな。

今はbabel-nodeで動かす

ECMAScript6で、node.js/express.jsのサーバ側を書く段になるとes6的メリットを享受できていません。Reactの方はclassを用いて書くといい感じなんですけどね。

// server.es6
import express from 'express';
const server = express();
server.get('/', async (req, res, next) => {
    res.status(200).send("Hello World!");
});
server.listen(8080);

$ node server.es6

で、こんなHello Worldでもそのままでは動かない。node.jsの今のバージョンであるv4.1.1だとModulesがデフォで対応していないのでimportでランタイムに文法エラーが出てきちゃう。そのうち対応するでしょうけど今は残念ながら--harmonyオプションでもダメなので、babel-nodeの出番です。babelをインストールしたら、/usr/local/binにいます。

$ babel-node server.es6

はい。動きました。しかし今使ってるv11プレビュー版のWebStormであるバージョンWS-143.380.11のデバッグ機構ではbabel-nodeを設定して一度目の実行はちゃんと動くけど、二度目以降はツールの中でポートを掴んでる何かにアプリケーションをちゃんとアタッチできないのか、WebStormが出す独自の404画面になっちゃう。これは多分WebStormのバグだな。babel-nodeでの実行をやめ、Transpileしてからnodeでの実行に戻しても同じ現象だし。コンソールでの起動ならば何の問題もない。

f:id:masataka_k:20151031095730p:plain

ちなみにこれはWebStormのデバッグ機構にbabel-nodeを設定するところ。一度目はちゃんとデバッグできるんだけどなー。

importの解決でBrowserifyからのBabelify

ECMAScript6でクライアント側を書いてますが、極小のHello Worldレベルから一歩進むだけで壁が。JavaScriptの発展の歴史の中で大きなアプリケーションを作るためのパッケージ参照機構が様々用意されてたみたいですが、そこにまた大きな曲がり角が出てきちゃってた。

  • 原始時代より長らくHTML上でscriptタグのsrc属性にファイル名を指定することで読み込むという参照方法のみだった。
  • サーバ側でもJavaScript使おうよとNetscapeを始め何度か繰り返されては散り、しばらく忘れ去られていた太古の試みは、Node.jsの登場でルネッサンス
  • Node.jsがプラットフォームとして実装したrequireでnpm管理されたパッケージ構成を参照させたのを模して、独自にライブラリレベルでrequireをクライアント側へ導入する文化が花開く
  • 産業革命。ES6でimport/exportの言語表記が標準化される。Node.jsもio.jsとの分離と再統合を経てES6に対応。しかし環境として世に数多あるブラウザがES6対応版に置き換わってくるのはこれから

今はimport書いてもそのままでは動かない。Babelではimportをrequireに書き換えるからrequireをbabel/registerなどでライブラリとして手当しないといけない。そんな中、そもそもファイルが分割されているから参照機構が必要なんじゃないかと動乱の中でちゃぶ台ひっくり返したのがBrowserifyでした(この前知ったばかりなのに適当な説明しちゃったな)。

http://browserify.org/

Transpilerとしてrequireが書いてあるコードの参照を辿ってライブラリを探し、見つけたもの皆を一つのファイルに合体させちゃうという。イイね!さらにTranspile時にBabelしちゃいましょうというのがBrowserifyのプラグインであるBabelify。合わせ技でimport/exportを解決。

github.com

Githubサイトの説明に書いてある通り、ターミナルでコマンド一発です。

$ browserify App.jsx -o public/js/bundle.js -t babelify

あとはHTMLのsrcで参照するタイプのscriptタグを全部削って、生成物を参照するscriptタグだけに変える。その前にライブラリはできる限りnpm管理でnode_module配下にしとくこと。

<script src="js/bundle.js"></script>

こうなってくると、WebStormのファイル監視にBabelを設定して自動Transpileしていたのを外して、動作確認が必要な時にBrowserifyを叩くってことになりますね。複数ページになって複数のブロック構造になってくるとまたどうしたもんかねとかあるけど、この辺は愚直にやっても数はブロック数に限定されてるので手間じゃなくいいんだと思う。

ECMAScript6を楽に

少々触った程度ですが、もうWebStormからSublimeやBracketsといったテキストエディタ+へは戻れません。だいたい最近の流行をつかんだところで、もっと中身の方に移ろうと思います。

babeljs.io

JSX書くのに、BabelがいるってんならECMAScript6もいいよねってことで。まずWebStormにBabelを設定しました。色々見て回る途中にBabelは入れてたので、設定もドキュメントに従ってすぐに終わります。

f:id:masataka_k:20151023045932p:plain

自動で平たいJavaScriptコンパイル(最近はトランスパイル/Transpileともいうらしい。トランスレートとコンパイルからの造語かな?)されるようになりました。実際の生成物は同じフォルダに出力されますが、WebStormのプロジェクト構造ビューでは元のファイルの添付のようにフォルダ表示されるようになります。全部自動だからイイね。生成ファイルはデフォルトだと「-compiled」接尾辞のついた名前。HTMLから参照するときには生成ファイルの方になるので、設定変えてもっと短くしたり出力フォルダを変えたりしたほうがいいかも。

ということで、ES6を書いた後が楽になりました。書くところはまだES6以前にJavaScript的な一切がほぼこれからなので楽じゃないんですけど。

var Hello = React.createClass({
  render: function() {
    return (
      return <h1>Hello, {this.props.name}!</h1>
    );
  }
});

ES5のこれより

class Hello extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}!</h1>
    }
}

ES6のこっちの方が見通し良い。