2016年4月13日水曜日

ECMAScript2015 (ES6) のまとめ / Promise, Set, Map

mainpicture

さて、しばらく間があいてしまいました。ES6の最終話です。

前回までの話で大きなところは大体抑えられたかと思います。今回は、いままで外部のライブラリ等に頼っていた部分がES6にて標準実装になったモノがあるのでその辺りをまとめていきます。

より詳細を確認したい方はECMAScript2015の公式を確認すると良いかと。


ECMAScript2015のまとめ

ES5と比較した時のES6の特徴としては以下のものが挙げられます。(言語仕様を読むと他にもいろいろと定義はされています)

  • let
  • template string
  • arrow function ( => )
  • default parameter ( function(x, y=10) { )
  • spread operator ( ...[1,2,3] )
  • rest parameter ( function(x, y, ...z) { )
  • class
  • import, export
  • Promise
  • Set, Map(今日はここまで)

今回はPromise, Set, Mapを説明していきます!


Promise

webサービスを開発したことがある方なら触ったことがあるかもしれません。非同期処理を書いていく際にcallbackでネストが深くなってしまうような箇所をthen関数やcatch関数を利用することにより、浅く見通しよく書けるようになります。

angularJSではqというライブラリが利用されていたと記憶しています。

まずはES5にてネストがいくつか連なっている例を挙げると、

// ES5 ネスト
var func1 = function(cb) {
  console.log('in func1');
  cb();
}

var func2 = function(cb) {
  console.log('in func2');
  cb();
}

var func3 = function(cb) {
  console.log('in func3');
  cb();
}

func1(function() {
  func2(function() {
    func3(function() {
      console.log('last call');
    });
  });
});

このような形になります。私は別にこれで書いても全く悪くないとは思っていますし、azureのnodejsのsdkの内部にもこういった内容が結構あります。ただ、例外処理などが入ってくると徐々に書きにくくなっていったりするのも事実です。

callback形式で例外処理をするさいには、callbackの第一引数にErrorを詰めて返すのが割とよく見られるやり方です。

func1(function(err) {
});

このような形になります。他のやり方として、第二引数に例外時の関数を渡したりという設計も可能ですが、個人的にはかなり読みにくくなるのであまり好きではなありません。

func1(function() {
// 正常処理
}, function(err) {
// 例外処理
});

さてこれをES6のpromiseを使って書き換えてみます。

// ES6 promise
var func1 = function(cb) {
  return new Promise(function(resolve) {
    console.log('in func1');
    resolve();
  });
}

var func2 = function() {
  console.log('in func2');
}

var func3 = function() {
  console.log('in func3');
}

func1().
then(func2).
then(func3).
then(function() {
  console.log('last call');
});

promiseを使うことにより、func2とfunc3がcallbackを呼び出すという役割は不要になりました。シンプルにその関数で必要な処理を記載するだけです。

値を次のfunctionへ渡していきたい場合にはreturnをすれば次の関数で受け取ることができます。

var func2 = function() {
  console.log('in func2');
  return 'from func2'
}
var func3 = function(message) {
  console.log('in func3');
  console.log(message);
}

また、エラー処理も読みやすく書くことができます。

// ES6 promise
var func1 = function(cb) {
  return new Promise(function(resolve) {
    console.log('in func1');
    resolve();
  });
}

var func2 = function() {
  console.log('in func2');
  throw new Error('oh no');
}

var func3 = function() {
  console.log('in func3');
}

func1(). // in func1
then(func2). // in func2
then(func3). // ここは実行されない
then(function() { // ここも実行されない
  console.log('last call');
}).catch(function(e) {
  console.log(e); // [Error: oh no]
});

catch関数を呼ぶことにより、読みやすい例外処理を行うことが可能です。

実はnew Promiseの引数に渡している関数の第二引数にrejectを渡すことも可能です。rejectを利用するとfunc1から明示的にcatch関数まで飛ばすことができます。

var func1 = function(cb) {
  return new Promise(function(resolve, reject) {
    console.log('in func1');
    if (true) {
      reject();
    }
    resolve();
  });
}

ただし注意すべき点があります。それは、ここでrejectした場合、thenの第二引数で処理を受けることも可能であるということです。

func1().
then(func2, func3).
then(func3).
then(function() {
  console.log('last call');
}).catch(function(e) {
  console.log(e);
});

このような形になっている場合、func1の後の処理は2行目のfunc3に渡ることになります。

Promiseに関してはこれくらいです。慣れるまで少し時間がかかりますが、比較的読みやすくなるので個人的には好きです。構文がrubyのblockに少し似ていて(chainはできないけど)そこまで読みにくさは感じませんでした。


Set, Map

二つまとめていきましょう。SetとMapについてです。

まずはSet。

Setは重複しない値を保持するためのデータ構造になります。

var s = new Set([1, 2, 3, 'aaa', 'bbb']);
console.log(s);
console.log(s.entries());
s.add(1)
s.add('ccc')
console.log(s);
var s = new Set([1,1,1,1]); // Set[1]
console.log(s);

これで大体概要はつかめるのではないでしょうか。重複した値を保持しないようにできているため、大量データをfilterしながらループ処理するときなどSetとaddを使えば重複する可能性などを考えずシンプルにコーディングすることができます。(arrayのfilter使えよという話もありますがw)

ちなみに値が同一であるというのはECMAScript6 7.2.10SameValueZeroで判定されているようです。興味のある方は目を通してみると良いかと思います。

次はMap。

オブジェクトと少し似ているのですが、keyに関数を指定したり、getやsetやkeyの取得などの関数が用意されています。

var data = new Map([
    ['a', 'aaa'],
    ['b', 'bbb'],
]);

console.log(data);
console.log(data.get('a'));
console.log(data.size);

for (var k of data.keys()) { // Iterator
  console.log(k);
}

と例を出してみたものの、個人的にはオブジェクトで十分であるような気もしています。そこまでこの構造がないと困ったというような局面になったことがないので、いまのところ有用性があまりわかっていないです。keyを取得するのが簡単になったのはかなり嬉しいですが。(これまではObject.keys(data)とかやってた)

最後の最後に肩透かしで申し訳ない。


さて、これにてECMAScript6のメインとなりそうなトピックの説明が完了しました。いろいろな言語がありますが、Chrome or node(V8), Firefox(Gecko?最近変わった??), Safari(webkit)などメジャーな実行環境・実装がいくつもある言語もそうないのではないかと思っています。しかも開発する際にはある程度気を配る必要があるという…。そんな中で標準というものが出来上がってきつつあり、プログラマとしては非常にありがたいです。

jsに関してはこれからも出来ることが増えていくことに期待しています。言語的には非常に柔軟で、スモールプロダクトであればかなり早く、かつ、クライアントもサーバーも同じ言語で記述できるという良い特性を備えつつあると思い活用しています。

次は今作っているwebサービスの話なんかができれば良いかな。

では、良いインプットと良いプログラミングを。

2016年3月28日月曜日

ECMAScript2015 (ES6) のまとめ / class, import, export

mainpicture

あと2回でES6に関しては終了です。次回はdockerかpythonによる機会学習あたりのネタを書こうかな。haskellでもいいな。IoTネタも少し書きたい。

前々回はES6に追加されたシンタックスシュガーや変数宣言の方法、前回はパラメータの渡し方や展開の新しい方法についてまとめました。

今回は、いよいよES6のメインかもしれないClassとモジュールについて取り上げます。

より詳細を確認したい方はECMAScript2015の公式を確認すると良いかと。


ECMAScript2015のまとめ

ES5と比較した時のES6の特徴としては以下のものが挙げられます。(言語仕様を読むと他にもいろいろと定義はされています)

  • let
  • template string
  • arrow function ( => )
  • default parameter ( function(x, y=10) { )
  • spread operator ( ...[1,2,3] )
  • rest parameter ( function(x, y, ...z) { )
  • class
  • import, export(今日はここまで)
  • Promise
  • Set, Map

今回はES6におけるclassとそれらを含むmodule importやexportをまとめます。


class

ES5以前でも同様のclassのようなものを作ることはできました。しかし、イマイチ読みづらく書きづらい。prototypeの理解をしないと少しイメージがしずらかったり、スコープを限定して書くこともやりにくく野良化しやすいです。

実例を出した方が理解が早いですね。まずはES5 ver。

// ES5

var User = function(name, mail) {
  this.name = name;
  this.mail = mail;
};

User.prototype.getName = function() {
  return this.name
}

var user1 = new User('Test User1', 'test1@test');
var user2 = new User('Test User2', 'test2@test');
console.log(user1.getName()); // Test User1
console.log(user2.getName()); // Test User2

// 後から宣言も可能
User.prototype.getMail = function() {
  return this.mail
}

console.log(user1.getMail()); // test1@test
console.log(user2.getMail()); // test2@test

// overrideも出来る
user1.getName = function() {
  return "<<" + this.name + ">>";
}
console.log(user1.getName()); // <<Test User1>>
console.log(user2.getName()); // Test User2

このように割と自由度高くどこでも再定義が可能で、prototypeを使ってmethodを定義するという形になります。(prototypeに関しては他の記述に任せますが、すべてのFunctionObjectの雛形のようなもので、newで生成したinstanceのクラスのbaseになるものです。)

さて上記例をどうようにES6で書き換えると次のようになります。

// ES6

class User {
  constructor(name, mail) {
    this.name = name;
    this.mail = mail;
  }

  getName() {
    return this.name;
  }
}

var user1 = new User('Test User1', 'test1@test');
var user2 = new User('Test User2', 'test2@test');

console.log(user1.getName());
console.log(user2.getName());

// これももちろんできるが、クラスに書くべし
User.prototype.getMail = function() {
  return this.mail
}

console.log(user1.getMail());
console.log(user2.getMail());

user1.getName = function() {
  return "<<" + this.name + ">>";
}
console.log(user1.getName());
console.log(user2.getName());

これで他の言語と同様にclassを定義することができるようになりました。

とはいえそれでも自由度の高い言語であることは変わらず、ダッグタイピングも可能ですし、そこでどんな定義をしても変数を突っ込んでも動かそうと思えば動かすことができます。

個人的にはthisあたりも冗長であることや、javascriptのthisは少し混乱を招きやすいのでthisは書きたくないなと感じています。


import, export

さて、Classを使えばある程度まとまった形でプログラムを書くことができるようになります。しかし、javascriptは依存関係の解決がとても難しい言語でした。htmlのhead内とかに記載しているため、どこでどの関数、変数が定義されているのか、参照しているソースコードはどれなのかとても追いにくく、関数名のバッティング等にも気をつける必要がありました。

しかし、ES6には標準でmodule化、importやexportの仕組みが用意され、お互いの依存関係を容易に示すことが可能になりました。

nodejsではrequireとmodule.exportで記載されていますが、ほぼ同様のことができるようになっています。

さきほどのUserクラスの簡易版をmodule化し、他のコードから読み込む場合は以下のようになります。

// lib.js

'use strict';

class User {
  constructor(name, mail) {
    this.name = name;
    this.mail = mail;
  }
  getName() {
    return this.name;
  }
}

export default User;
// main.js
'use strict'

import User from './lib';

var u = new User('hogehoge', 'hogehoge@hoge');
console.log(u.getName());

このような形になります。

自作のlibを参照する際には./で始めると相対パスで依存関係を解決しにいきます。npmでインストールしたlibなどはimport _ from 'lodash'import async from 'async'などのようにそのままimportすれば利用可能になります。

nodeの場合はimport、exportの記法が多少異なる( requireとかになる)のでご注意ください。あくまでもES6の標準です。

これで依存関係の把握が容易になり、global汚染もかなり解決されるようになりました。


私はできるだけ短く、できるだけ簡単にソースが書けるように頑張っております。すっきり書けるととても面白いものです。

さて、次回はES6に関しては最後になります。取り上げるのはES6で組み込まれたpromiseとset, mapに関してです。promiseは少し独特な動きをするので慣れるまで大変かもしれませんが、結構便利なものです。読んでいただければと思います。

では良いインプットと良いプログラミングを。

2016年3月22日火曜日

ECMAScript2015 (ES6) のまとめ / default parameter, spred operator, rest parameter

mainpicture

前回に引き続き、ES6の変更点を見ていきます。

より詳細を確認したい方はECMAScript2015の公式を確認すると良いかと。


ECMAScript2015のまとめ

ES5と比較した時のES6の特徴としては以下のものが挙げられます。(言語仕様を読むと他にもいろいろと定義はされています)

  • let
  • template string
  • arrow function ( => )
  • default parameter ( function(x, y=10) { )
  • spread operator ( ...[1,2,3] )
  • rest parameter ( function(x, y, ...z) { )(今日はここまで)
  • class
  • import, export
  • Promise
  • Set, Map

前回は新しいarrow functionや変数宣言の新しいスコープであるletなどを取り上げました。

今回は、関数へ渡す引数やパラメータ展開の話がメインになります。


default parameter

rubyやpythonに見られるデフォルト引数という仕様です。引数に値が渡されなかった場合にこちらの値を利用することになります。メソッドのオーバーロードを書かなくて済むという利点がありますが、実際にどんな引数が渡ってきているのかを把握しておかないと何故か動いてしまうというソースを書いてしまうことになりかねないので注意が必要です。

ES5で実現するためには関数内で引数を取得するargumentsと呼ばれるオブジェクトを利用したり、関数名を変えたりしていることかと思います。

// ES5
var checkarg = function(i1,i2) {
  console.log(i1) # 1
  console.log(i2) # 2
  console.log(arguments['2'] || 'def') # def
}
checkarg(1,2)

これを次のように書き換えることができるようになりました。

// ES5
var checkarg = function(i1,i2,i3='def') {
  console.log(i1) # 1
  console.log(i2) # 2
  console.log(i3) # def
}
checkarg(1,2)

Firefoxには実装済みのようですが、chromeとnodeでは動作が確認できませんでした。

このデフォルト引数は、スクリプト言語では割と有用だと個人的には思っています。確かにメソッド内部で値の存在判定を行い、デフォルト値っぽいのを使うことができるのですが、多少煩雑ですし何を意図しているのか分かり難い感じになってしまうため、これを使えるとソースコードが読みやすくなりますね。


spread operator

私がこの演算子に出会ったのはreact-nativeを使ってアプリを実装しているときでした。react-nativeのNavigatorSceneConfigsのtransitionで使われています。

var FadeToTheRight = {
  ...FadeToTheLeft,
  transformTranslate: {
    from: {x: 0, y: 0, z: 0},
    to: {x: Math.round(SCREEN_WIDTH * 0.3), y: 0, z: 0},
  },
  translateX: {
    from: 0,
    to: Math.round(SCREEN_WIDTH * 0.3),
  }
};

・v0.15だったので今は違うかもしれないですが。
・上の例はES7のproposalで、babelがpluginを用いて利用可能にしています。純粋なES6の実装では使うことができません。

...FaceToTheLeftというのが中にあるのが確認できます。FadeTransactionの基本的な設定のようなものが、FaceToTheLeftに定義されていて(遷移の最後のほうでopacityが減るなど)、FadeToTheRightで一部変更がある部分を定義していることになります。

もう少し簡単なspread operatorの例を挙げてみましょう。

// ES5
var array1 = [1,2,3]
console.log(array1);
var array2 = [4,5,6]
console.log(array2);

console.log(array1.concat(array2)); 

最後のarray1の連結部分が

// ES6
var array1 = [1,2,3]
console.log(array1); // [1,2,3]
var array2 = [4,5,6]
console.log(array2); // [4,5,6]

console.log([...array1, ...array2]) // [1,2,3,4,5,6]

と、こんな風に書くことができます。

かなり見通しよく書けるようになりました。割とこの辺りはlodashとかを利用して 書くことが多いのですが、標準仕様で使えるようになるとありがたいですね。


rest parameter

最後はrest parameterについてです。他の言語だと可変長引数で同様のことができるようになっていたりします。

むしろjavascript今までできなかったのかよ的な雰囲気すらあります。おそらくこれまではargmentsでどうにかしていたのでしょう。

こんな感じになります。

//ES6
function expose(a1, ...rest) {
  console.log(a1) // 1
  console.log(rest) // [2,3]
}
expose(1,2,3);

node5系で動作させる場合には、コマンドに「–harmony_rest_parameters」というオプションを入れる必要があります。
node --harmony_rest_parameters hogehoge.js

残りのパラメーターが配列で入ってくるのが楽ですね。長さを気にせず渡すことができます。
(あまり長すぎるのも可読性が落ちるので気をつけましょう。)


今回は引数に関して主にまとめてみました。個人的にはbabelで利用出来るspread operatorのhash版が結構使えるやつなのでオススメです。

特にreateを実装しているとstateを書き換える部分があるのですが、そこを読みやすくかけるので。

this.setState({
  ...this.state,
  field: newValue,
});

さて、次回からES6のクラスとモジュール周りをまとめていきます。

では良いインプットと良いプログラミングを。

2016年3月14日月曜日

ECMAScript2015 (ES6) のまとめ / let, template string, arrow function

mainpicture

これをお読みの方は、どんな言語を用いて開発を行っているでしょうか。

javascriptはひと昔前までは主にブラウザ上で動きをつけるために利用され、私の感覚からするとその頃はとても書きにくい理解しづらい言語でした。(自分のプログラムの能力のせいもありますがw)しかし、nodejsが流行り始めた5,6年くらい前くらいからjavascriptはかなり勢いを増し、現在でもその勢いは大きく落ちることなく、結果多くの方が利用するようになっているのではないかなと思います。

最近ではnodejsも多くのPaaSの上で動作することが可能となり、バックエンドがjavascriptで記述することができるようになったため、フロントエンドとバックエンドで使用する言語を同一にするという目的のためにjavascriptを選択するのも良い選択になりました。

さらに、フロントエンドの開発が進んでいくうちにAngularJSreactといった数々のフレームワークが生まれ、SinglePageApplicationという考え方が生まれました。これらのフレームワークは今でも積極的にアップデートが重ねられ、リッチなWebアプリケーションを構築する際に大きく役に立ちます。

しかし、通常のjavascriptを利用して開発を行うと、言語仕様のために記述がしにくい箇所がでたり、冗長になったり、ソースコード量が多くなってしまうことがありました。それらを補うためにcoffeescripttypescriptなどが開発されるようになりました。

しかし昨年ECMAScript2015が制定され、ChromeやSafariなどの多くのブラウザによる実装が進んでいるため、coffeescripttypescriptで行われていた多くの部分を標準仕様で解決することができるようになりました。一部実装していないブラウザに関してはトランスパイラと呼ばれるES6をES5に変換するBabelのようなものもあるため、ES6は積極的に利用すべきであると思います。

そこで今回はECMAScript2015を数回に分けて紹介しつつまとめていきます。


ECMAScript2015のまとめ

ES5と比較した時のES6の特徴としては以下のものが挙げられます。(言語仕様を読むと他にもいろいろと定義はされています)

  • let
  • template string
  • arrow function ( => ) (今日はここまで)
  • default parameter ( function(x, y=10) { )
  • spread operator ( ...[1,2,3] )
  • rest parameter ( function(x, y, ...z) { )
  • class
  • import, export
  • Promise
  • Set, Map

さて、少しずつみていきましょう。


let

ES5では変数宣言の際には

// ES5
var s = "I'm string"
var i = 123

このような形で定義していましたが、スコープが大きくなってしまうという問題がありました。

// ES5
function m() {
  var s1 = "s1";
  console.log(s1); // s1
  if (true) {
    var s1 = "changed";
    console.log(s1); // changed
  }
  console.log(s1); // changed
}
m();

これをlet宣言を使うことによりスコープをレキシカルブロック({}で囲まれた範囲)に宣言することができるようになりました。

// ES6
function m() {
  let s1 = "s1";
  console.log(s1); // s1
  if (true) {
    let s1 = "changed";
    console.log(s1); // changed
  }
  console.log(s1); // s1 ←変わらない!
}
m();

これでかなりスコープを限定して記述することが可能になりました。

swiftだとletは再代入不可ですが、ES6のletは再代入が可能です。
ES6の定数はconstで宣言します。


template string

これは他の言語にも良くある形だと思います。文字列フォーマットとも呼ばれているかもしれません。(厳密には文字列フォーマットは数値・文字の判別や数値の桁数などができるので違うかもですが)

// ES6
let pc = "Mac";
console.log(`I'm using ${pc}.`); // I'm using Mac.

こんな形で文字表現がかなり読みやすくなりました。これまでは+で連結しまくっていたので非常に楽です。


arrow function

むしろ今までなくて良くやってこれたなという雰囲気すら感じますが、関数宣言のシンタックスシュガーといえば良いでしょうか。

他の言語だとjava8から実装されたため同様の記法がjava8から使うことができ、Swiftにも同じような構文があります。RubyのProcオブジェクトやブロックとも似ています。

javascriptという性質上サーバーサイドとの連携が多く、遅延が発生するためそういった箇所はcallbackの嵐となるため、それをシンプルに書けるようにするためにはこのarrow functionが欲しくなってきます。

さて、どういったものなのかというと

// ES6
let f = () => {
  console.log("f is called");
}
f(); # f is called

これですね。少しややこしいケースも書いてみましょう。

// ES6
function post(data, callback) {
  setTimeout(() => {
    console.log(`Handle Data: ${data}`);
    callback({isOK: true});
  }, 3000);
}

function postDataToServer(data, callback) {
  post(data, callback);
}

function webapi(data, callback) {
  postDataToServer(data, callback);
}

console.log("Before Post");
webapi("Post Data", (response) => {
  console.log(response.isOK ? "Succeed!!" : "Failed...");
});
console.log("After Post");

このような形で時間のかかる処理(この例だとpost)の処理結果をcallbackが引き受けて、それをarrow functionで定義しておくとソースコードは短くてすみます。

別に読みにくければfunctionで代入も可能です。

// ES5
webapi("Post Data", function(response) {
  console.log(response.isOK ? "Succeed!!" : "Failed...");
});

これでもやっていることは全く変わりません。

ライブラリとかだと、第一に引数にerrorを渡し戻すものも多く存在します。また、この例のようにメソッドが一つくらいであればいいのですが、successとfailで二つのfunctionを定義しなくてはならなくなると徐々にめんどくさくなってきたりします。


さて、いかがでしたでしょうか。

今もまだ昔のjavascriptの書き方をしているのであれば、これを機に少しずつES6の書き方に変えていくのが良いかと思います。
次回は関数へのパラメータ周りを紹介していきます。

では良いインプットと良いプログラミングを。

2016年3月8日火曜日

Go言語とGoogleAppEngineに触れる / TaskQueue, PushQueue, PullQueue

mainpicture

Webサービスの処理に時間のかかる部分がある場合、それをQueueに入れて別プロセスのWorkerに処理をさせたいという部分があります。

例えば、メールを送信する処理であったり、画像の変換だったりですね。

Go言語においては最初はGo-Routineを利用すれば、非同期に処理ができたり、responseを返した後に処理を継続するということができるかなと思っていたのですが、responseが終了した時点でContextが閉じられてしまうせいなのかうまく動作せず、このあたりの方法を探っていたところでした。

Google App Engineには標準でQueueとWorkerを実行するための仕組みが備わっています。今日はその辺りをまとめていきます。


TaskQueue, PushQueue, PullQueueの違い

私は最初この三つは異なるものだと思っていましたが、TaskQueueの種類としてPushQueuePullQueueがあるという説明が載っていました。ということでPushQueuePullQueueの違いを見ていきましょう。


PushQueue

PushQueueは以下のような仕組みを備えています。

  • ProcessingRateという処理速度を用いてTaskを処理する。
  • App Engineが処理容量に応じて自動的にスケールする。
  • 処理が終わったら自動的にqueueを削除する。

特徴的なところは、

色々な値をデフォルトで設定し(queue.yamlで設定可能)App Engineが自動的に実行し掃除までしてくれる

というところです。

ちなみにApp Engine外のサービスから触ることはできません。


PullQueue

PullQueueは以下のような仕組みを備えています。

  • アプリケーション外のコードや他のアプリケーションがTaskを処理することが可能。
  • 処理時間やタイムフレームを明確に決めることができる。
  • 処理のボリュームにより自分でスケールさせたり、Queueの削除をする必要がある。

一般的なキューイングのシステムはこちらのイメージが近いです。単純なStackのイメージになります。

PushQueueと違い自動で実行はされません。


Go言語でPushQueueを使ってみる

現在開発しているサービスにてPushQueueを使う部分があったので実装例と詰まったところとかを記載していきます。

今回はApp Engineのdelayパッケージを利用しました。

サンプルコード

package main

import (
    "golang.org/x/net/context"
    "google.golang.org/appengine"
    "google.golang.org/appengine/datastore"
    "google.golang.org/appengine/delay"
    "net/http"
)

func init() {
    http.HandleFunc("/", main)
}

var delayPut = delay.Func("delayPut", func(c context.Context, data *Data) {
    keys := Put(c, validData)
})

var Put = func(c context.Context, data *data) []datastore.Key {
    // PutEntity
    keys, err := data.put(c)
    return keys
}

func main(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    delayPut.Call(c, data)
}

こんな感じになりました。

実装上の注意

さて上のサンプルでも利用しているdelayパッケージに関してですが、少し注意点があります。

  • FuncはTop-Levelのコンテキストで呼ぶこと。
    もし仮に、上記mainの関数内でdelayPutを定義したりすると、Task実行時にこのファイルがロードされても、mainの中身までロードする訳ではないため、delayPutが見つからず失敗するためだと思われます。

  • Funcの第二引数の返り値に意味があること。
    Funcの第二引数funcの返り値は複数返すことができますが、最後の値がerror typeであり、かつnilではない場合はもう一度そのメソッドを呼ぶという意味になります。

    実は、PushQueueは失敗したら設定した回数だけリトライされます。

  • テストが少し実装しにくいこと。
    delayFuncを呼んでいるmain()のテストが予想通りになりませんでした。time.Sleepしても、StronglyConsistentDatastore: trueにしても、datastoreへの更新がうまく動かなかったためです。そのため、Put()のように別なメソッドへ切り出して、その部分に対するテストを実装しています。

  • default queueにしかqueueを入れられないこと。
    調べたのですが出てきませんでした。メソッド内に特別入れられるような箇所もなく、delayパッケージではdefault queueにしか積むことが出来ないのかもしれません。おそらくTask Queue APIを利用すれば出来るかと思いますが、今回はそこまで触れませんでした。


queue.yaml

PushQueueの設定をするときは、queue.yamlを作成します。(以下はdefault queueに対する設定を行っています。)

細かい設定内容や詳細はGo Task Queue Configurationに載っています。

queue:
- name: default
  rate: 500/s
  bucket_size: 100
  max_concurrent_requests: 1000

今回はリトライに関するパラメータを一切設定していません。

設定を反映するときは

appcfg.py update_queues myapp/

を実行します。

rate, bucket_size, max_concurrent_requests

設定内容でとても分かりにくかった項目があります。少し私なりの補足を書いていきます。

bucket_size
これから理解するのが一番早いです。bucketとはバケツの意味であり、一度に運べる量みたいなものだと思ってください。(リボルバーの銃弾の装填数の最大数もイメージとして近いです。)
そのため、bucket_size=5のところにqueueが8個きた場合には、5個はすぐに処理用のマシン(プロセッサ)へ渡されますが、残りの3個に関してはbucketに空きが出るまで処理されません。(弾を5発詰めて発射します。残りは3個ですね。)
この仕組みは大量データ(大量queue)に対して、処理量を一様にするためにこのようになっています。もし仮にこの仕組みを採用しないとすると、処理をするマシン(プロセッサ)の数を都度増減させるか、データが最も来るときに合わせてそれを処理できるくらいの数に設定しておかなくてはいけません。
rate
bucket_sizeの空きをどれくらいの頻度・個数空けるかという設定値になります。書き方としては、5/s、10/m、3/hなど個数/[s,m,h]を指定できます。(上のリボルバーでいうと装填速度みたいなものです。)どういった設定値なのかというと、例えばbucket_size=5のところにqueueが8個到達するという場合を考えてみましょう。

rate=1/sの場合 1. bucket = 5, bucket空き = 0, 残りqueue = 3 2. 1s後、bucket = 0, bucket空き = 1, 残りqueue = 3 3. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 2 4. 1s後、bucket = 0, bucket空き = 1, 残りqueue = 2 5. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 1 6. 1s後、bucket = 0, bucket空き = 1, 残りqueue = 1 7. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 0

rate=2/sの場合 1. bucket = 5, bucket空き = 0, 残りqueue = 3 2. 0.5s後、bucket = 0, bucket空き = 1, 残りqueue = 3 3. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 2 4. 0.5s後、bucket = 0, bucket空き = 1, 残りqueue = 2 5. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 1 6. 0.5s後、bucket = 0, bucket空き = 1, 残りqueue = 1 7. 直後に、bucket = 1, bucket空き = 0, 残りqueue = 0

このような流れとなります。bucketの回復速度みたいな感じですね。

公式の説明のキャプチャが分かりやすかったので貼っておきます。
enter image description here
max_concurrent_requests
単純に並列処理をする最大数です。datastoreがたくさんのrequestを受けて競合するのを避けるために使われます。
queue:
- name: optimize-queue
  rate: 20/s
  bucket_size: 40
  max_concurrent_requests: 10

この場合は並列で10ずつしか処理しないので、0.5sに10処理が完了すればqueueが詰まることはありませんね。かつ、並列処理を10に抑えているのでdatastoreの競合が起きにくいです。(多分それくらいなら発生しないかと)


さて、PushQueue, PullQueueに関しては以上です。
PullQueueを利用する機会があれば、そちらの方に突っ込んだ内容を書いてみようかと思います。

では良いインプットと良いプログラミングを。

2016年3月1日火曜日

Go言語とGoogleAppEngineに触れる / GoogleAppEngine

mainpicture

前回の記事でGo言語の第一印象とチュートリアルに関する気づきをまとめました。

今回はそれに引き続き、Google App Engineに関する気づきや詰まった点をまとめていきます。


Google App Engineに触れる

現在Google App Engineでは、java、python、PHP、Goをサポートしていますが、スピンアップが最も高速に立ち上がるのがGo言語のようです。

そのため、多くの方がGo言語Google App Engineへの利用を前提として使っているのではないでしょうか。

インストールや実行に関してはGoogle App Engine SDKなどを参照していただくことにして、実際に実装してきた中で問題となった箇所やその解決策を記載していきます。


json parse

webサービスの要件にrequest bodyにjsonがくるのでそれをparseして値を適宜処理するというものがありました。

他の言語やプラットフォームだと、body parserやjson parserがあり思っていた通りの動作をしてくれることが多いのですが、Google App Engineにはbody(io.Reader interfaceを持つ)を"encoding/json"json.NewDecoderへ渡し、structへ変換するという流れを取ります。

サンプルコードは以下のような形になります。

import (
    "encoding/json"
    "io"
)

type Data struct {
    Type     int      `json:"type"`
    Value    string   `json:"value"`
}

func ParseJson(r io.Reader) (*Data, error) {
    decoder := json.NewDecoder(r)
    data := Data{Type: -1}
    for {
        if err := decoder.Decode(&data); err == io.EOF {
            break
        } else if err != nil {
            return nil, err
        }
    }
    return data, nil
}

こんな感じになります。

struct`json:"type"`というようなtagをつけることにより、jsonのnameをstruct内のfieldにマッピングします。データの方はstructと一致している必要があります。

問題1. 文字列と数値

jsonではvalueに数値がある場合には”“で囲まないようです。そのため、”“が入力値となっている値をintにマッピングしようとするとエラーを出力します。(個人的にはzero valueという仕組みを持っているのだから0入れておいてよと思いますが。)

これはクライアントアプリとの仕様の問題なので、今回は全てstringで値をもらい、後から全て変換する方針にしました。

問題2. マッピングが失敗する

これの解決が一番時間がかかりました。ある特定のfieldだけ値のマッピングがされないという事象に遭遇したのです。

よく見てみるとtagの中にスペースが入っていることが分かり、これを消せばマッピングがうまく動いてくれました。

json: "type"json:"type"です。


カスタムヘッダー

今回オリジナルのヘッダーをクライアントが送信してくるので、サーバーがそれを取得して値を使うという実装がありました。実装自体はそんなに難しくなく、SDKもきちんとHeaderを取得するためのインターフェースを備えていました。

ソースコードしては以下のような感じです。

const MY_HEADER = "x-my-header"

func GetHeader(r *http.Request) string {
    h := r.Header[MY_HEADER]
    if h == nil {
        return ""
    }
    if len(h) == 0 {
        return ""
    }
    return h[0]
}

問題. カスタムヘッダーが取得できない

実際にテストコードは動き、ローカルサーバーも無事に動作することを確認しました。問題は本番環境にデプロイした後に発生しました。

Headerの取得ができない!!

Headerの内容をログに書き出してみると、Google App Engine内ではHeader Keyがキャメルケースになるようです。(RFCを読んでいないので詳細はわからないのですが)

そのため上記サンプルのx-my-headerX-My-Headerに修正する必要があります。

気をつけましょう。


datastore

問題. datastore console viewが正常に動作しない

ある構成のstructをdatastoreに入れたところ、console viewからデータが確認できなくなってしまいまいした。

これに関しては現在調査中で、実際に値が入っているかどうかはwebサービスにendpointを一つ用意して、そのendpointロジック内でqueryを発行して結果を出力して確認できるようにしましたが、いまいちです。

情報. Ancestor(祖先)は自動生成

datastoreの概念にancestorというentityのグルーピングや親子関係を表現するための仕組みがあります。ソースコードではdatastore.NewKey(c, "datastorename", "", 0, ancestor) のようにkeyを生成する際に第5引数に入れます。

このancestor keyに関しては、もし存在しない場合には自動で生成されるので、先に作っておいたりする必要はありません。


interface{}型

Go言語でのinterfaceの定義の仕方は

type Interfacer interface {
    Method() string
}
func (d *Data) Method() string {
    return "hogehoge"
}

このような形でした。interfaceとはメソッド群を定義しています。言語仕様を読むと、全ての型はinterface{}という空のinterfaceを実装しているとの記載があります。

このinterface{}はjavaでいうObject型のような扱いをすることができ、かなり抽象度を上げた取り扱いをすることができます。
また、以下のような形でinterface型から値を取り出し、 それぞれの型に適した処理を実装することができます。

switch i := x.(type) { // xがinterface{}型
case nil:
    printString("x is nil")
case int:
    printInt(i)  // iはint
case float64:
    printFloat64(i)  // iはfloat64
case func(int) float64:
    printFunction(i)  // iは関数
case bool, string:
    printString("type is bool or string")
default:
    printString("don't know the type")
}

testing

前回の記事でも簡単に取り上げましたが、datastoreのテストをする際に一貫性というキーワードが大切になります。これはdataの性質を表すACIDの一つであり、処理が正しく行われた場合は、その処理は一貫した結果を返すという類のものです。

なので誰かがデータを更新した直後に他の人がデータを読みにいったケースを考えると、一貫性が保たれているシステムの場合は更新したデータを100%読むことができます。

例えば金融機関、人事給与の給与データなどはそういう設計をすべきです。

一貫性が保たれていないシステムの場合は、書き込みの直後に読み取りにいってもデータが変わっていない可能性があります。

例えば、ログデータであったり、チャット内容であったり、DNSレコードの更新であったりは、書き込みしたという事実があれば、読み取りは直後でなくてもよいという性質があります。

さて、Google Cloud Platformのdatastoreは一貫性を保証していません。(厳密言えばqueryにancestorを指定した場合など、強い一貫性を持たせて実行することができる箇所もあります)そのためテストを行う際に問題が生じます。それはテストロジックの最初の方でデータをinputするロジックを追加したのに、テストメソッドの箇所では反映されておらずまともにテストができないということです。

今回これにはまりました。

解決策はaetest.NewInstanceを生成する際にオプションでStronglyConsistentDatastore: trueを追加する必要があります。

確かにこのオプション名は強い一貫性のdatastoreにするという意味になりそうですね。

なのでContextを生成する部分を以下のように書き換えます。

opt := aetest.Options{
    AppID: "test",
    StronglyConsistentDatastore: true,
}
inst, _ := aetest.NewInstance(opt)
defer inst.Close()
req, _ := inst.NewRequest("POST", "/", nil)
c := appengine.NewContext(req)

このcを全てに使い回せば、一貫性を保ったテストを記載することができます。


いかがでしたでしょうか。個人的にはGo言語自体は嫌いではないです。ただし、自由度がかなり制限されていると感じています。誰が書いてもしばらくは割と似たようなソースコードになるのではないでしょうか。

また、rubyやnodeに比べると周囲のライブラリがまだ貧弱でやることは少し多くなるのかもしれないなと思います。

ただ、Google App Enginge上での速度は捨てがたいですけどね。

今日はここまでです。

では良いインプットと良いプログラミングを。

2016年2月26日金曜日

Go言語とGoogleAppEngineに触れる / Go言語

mainpicture

Go言語というものを仕事の関係上触ることになったので、そのキャッチアップした内容をまとめていきます。また、Google App Engine上での開発案件であったため、そこでハマったことなども合わせて書いていきます。

Go言語とGoogle App Engineに触れる

まずは、Go言語の雑感を簡単に。

  • 何の言語に似ているのかと言われれば、swiftに近いイメージ
  • でもかなりシンプル
  • 「go」「defer」「interface」「interface{}」「channel」が大切

幸いにして、Go言語にはとても手厚いチュートリアルが用意されています。

Go Tour

読みながら一通りやればある程度書けるようになります。素晴らしいですね!プログラミング言語の学習問わず、全ての学習をする上で「知ること」「聞くこと」「使うこと」という3ステップが大切です。Go言語の概要を知り、Go Tourを読み、動かしながら実際に書いていくようにしましょう!

ということで、実際にある程度やってみたので、その記録と感想を書きます。



Go Tour

だらだらと内容を併記しても内容が重複するので、気づいた箇所だけピックアップして記載していきます。

package

import (
    "fmt"
    "math/rand"
)

とかで記載します。ただ、そのコード内で利用していない場合はコンパイル時にエラーになります。ここが最初かなりしんどかった箇所です。

例えばデバッグ中にログを書き出したい時

import (
    "fmt"
)

func main() {
    v := 1
    v = v + 1
    fmt.Printf("The value is %v", v)    
}

これは大丈夫なのですが、

import (
    "fmt"
)

func main() {
    v := 1
    v = v + 1
}

これでエラーになります。毎回importまで戻って足したり消したりするのがめんどくさいです。 確かに不要なimportがなくなるのでコードは短くすっきりするし、依存関係も読みやすいのでありがたいところではあるのですが一長一短かと。

また、暗黙的な型宣言として

// これを
var v int = 1
// こう書ける
v := 1

といった記法があるのですが、良い点は型を意識しないでもある程度書けること、悪い点は何の型が入ってきているのかが分かりづらいことというのがあります。

例えば、

import (
    "google.golang.org/appengine"
)

func index(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    // 処理
}

これのcが何を表しているのかが分からないのです。何のパッケージをインストールしたらいいのやらという感じです。この段階だとコンパイルが通りますが、

import (
    "google.golang.org/appengine"
)

func index(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    sub(c)
}

func sub(c ????) {
    // 処理
}

この時点で結局importを書く必要があり、少し残念な気持ちになりました。

"golang.org/x/net/context"をimportすればcontextが解決され、???の箇所はcontext.Contextとすれば良いです。

function

頭文字を大文字にするとPublicの意味になります。

Go言語は高階関数を扱えるようになっていて、funcを引数に渡したり、closerを作ったりすることができます。javascriptやhaskellでも同様のことができますね。

実際に作成したCachedFinderを載せておきます。

package main

import "fmt"

func CachedFinder() func(key string) string {
    cache := make(map[string]string)
    return func(key string) string {
        str := cache[key]
        if str != "" {
            fmt.Println("got at cache")
            return str
        }
        str = "abc" + key // webapiやdb readなど
        if str == "" {
            return ""
        } else {
            cache[key] = str
            return str
        }
    }
}

func main() {
    fmt.Println("Hello, 世界")
    finder := CachedFinder()
    a := finder("hogehoge")
    fmt.Println(a)
    b := finder("piyopiyo")
    fmt.Println(b)
    c := finder("hogehoge")
    fmt.Println(c)
}

二度目のhogehogeはcacheから取得できるようになっています。

defer

deferをつけると、遅延呼び出しが可能になります。タイミングは呼び出し元の関数の終わり(returnする)まで遅延させるという機能です。

関数の引数はすぐに評価されるが、呼び出しがreturnの直後になる点に注意が必要です。deferが複数ある場合は、stackされるため実行は下(後に宣言した方)から呼ばれます。LIFO(last-in-first-out)です。

これに関しても良い点、悪い点があります。

deferの利用するポイントの一つとしては、Close()をNew()した直後に書くとかが多いですが、メソッドの終端で実行するという仕組みであるため、New()したものからさらにNew()をするという箇所では使えなかったのが残念でした。

Go App Engineのテストパッケージに"google.golang.org/appengine/aetest"というのがあり、InstanceからContextを生成する箇所があります。このContextだけ利用したいという箇所があるのですが、InstanceをCloseするとContextも使い物にならなくなるので、CreateInstance()みたいなのを定義しCloseは書けませんでした。

func TestHogehoge(t *testing.T) {
    opt := &aetest.Options{
        AppID: "test",
        StronglyConsistentDatastore: true,
    }
    inst, _ := aetest.NewInstance(opt)
    defer inst.Close()
    req, _ := inst.NewRequest("POST", "/", nil)
    c := appengine.NewContext(req)
    // 処理
}

これを関数として切り出して

func CreateInstance() context.Context {
    opt := &aetest.Options{
        AppID: "test",
        StronglyConsistentDatastore: true,
    }
    inst, _ := aetest.NewInstance(opt)
    defer inst.Close()
    req, _ := inst.NewRequest("POST", "/", nil)
    return appengine.NewContext(req)
}

とやると、使えないContextが出てきます。残念です。

ちなみに上記コードの意味はdatastoreの一貫性を強制的に保つようにするフラグを立ててContextを生成するものです。datastoreのテストをするとよくあるのが、writeの直後にreadすると値がreadできないという状況が発生するため、それを回避するために利用しています。

pointer

ポインタというか、Go言語は基本的には値渡しになります。なので、高コストのstructを生成したり、内容を変更したい時に使います。java界からきた私は最初少し違和感がありましたが、これはこれでまぁありかなという感じです。

例としては

package main

import "fmt"

func swap(s string) {
    s = "world" // これだとsは変更できない
}

func pswap(s *string) {
    *s = "world" // pointerを通して変更する
}

func main() {
    s := "hello"
    swap(s)
    fmt.Println(s)
    pswap(&s)
    fmt.Println(s)
}

slice

sliceがoverflowした時には新しい配列が割り当てられます。
以下二つの実行結果の違いで分かるかと思いますが、

package main

import "fmt"

func main() {
    fmt.Println("overflow")
    a := make([]int, 0, 5)
    a = append(a, 1, 2, 3, 4)
    printSlice("a", a)
    b := append(a, 1, 2, 3, 4)
    printSlice("b", b)
    a[0] = 100
    printSlice("a", a)
    printSlice("b", b)

    fmt.Println("not overflow")
    c := make([]int, 0, 100)
    c = append(c, 1, 2, 3, 4)
    printSlice("c", c)
    d := append(c, 1, 2, 3, 4)
    printSlice("d", d)
    c[0] = 100
    printSlice("c", c)
    printSlice("d", d)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}

前者(a,b)はoverflowしたタイミングで別なsliceを割り当てるので、
aとbが最終的に示す0要素目の値が異なるようになります。

後者(c,d)の方はoverflowしない(makeでcapacityを100にしている)ので
最後のcとdのsliceの0要素目が示す値が同じ(100)になります。

なので、私が実装している時は基本的にappendの結果は自身で受け取るようにしています。

a = append(a, 1,2,3)
こんな感じですね。

methods

Goにはクラスは存在しません。struct型や任意の型にメソッドを定義できるので、メソッドレシーバー(funcの後に続く t *Typeなど)という呼ばれているようです。

interface

javaのinterfaceとかに似ていますね、単純なメソッドの集まりを定義していきます。

ただし、実装側は明示的にimplementsとか書かなくてよく、シグニチャレベルで一致しているかどうかの判断をしているようです。

type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    v := Vertex{3, 4}

    a = &v // a *Vertex implements Abser

    a = v
    fmt.Println(a.Abs())
}

type Vertex struct {
    X, Y float64
}

// これダメ。 *VertexがAbsを実装している定義となり、上の a = vでコケる。
// func (v *Vertex) Abs() float64 {
//  return math.Sqrt(v.X*v.X + v.Y*v.Y)
//}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

fmtパッケージにあるStringerというinterfaceがあり

type Stringer interface {
    String() string
}

という定義がされている。

そのため

type Person struct {
    Name string
    Age int 
}

func (p Person) String() string { // ここでStringerの実装
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
    a := Person("Taro", 10)
    fmt.Println(a)  
}

とかが可能になります。

reader

何か作る上ではfile,webstreamのreadを取り扱う必要があるのは避けられないことです。Go言語にはデータストリームを読むことを表現するio.Readerインターフェースが存在します。

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Hello, Reader!")

    b := make([]byte, 8)
    for {
        n, err := r.Read(b)
        fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
        fmt.Printf("b[:n] = %q\n", b[:n])
        if err == io.EOF {
            break
        }
    }
}

tourそのままコピペしました。

出力結果が

n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""

こんな感じで出てきます。

Readerはbyte sliceに入れた数(n)とエラーを返しています。
fmt.Printfでbを見ると、きちんと1byte毎に分割されたHello, Reader!が出ていますね。

ASCII文字列の10進数で表現されているので対応表を載せておきました。
http://e-words.jp/p/r-ascii.html


さて以上が私がGo Tourをやってみた感想でした。

次回は、Google App Engineの開発でつまづいたところをまとめていきます。

最後に少しネタバレになりますが、Excersizeの解答例の一部を載せておきます。


Go Tour 回答例

Loops and Functions

package main

import (
    "fmt"
)

func Sqrt(x float64) float64 {
    z := 10000.0
    for i := 0; i < 10; i++ {
        z = z - (z * z - x) / (2 * z)
        fmt.Println(z)
    }
    return z
}

func main() {
    fmt.Println(Sqrt(2))
}

Slices

package main

import (
    "fmt"
    "golang.org/x/tour/pic"
)

func Pic(dx, dy int) [][]uint8 {
    fmt.Println(dx)
    fmt.Println(dy)
    ret := make([][]uint8, dy)
    for i := 0; i < dy; i++ {
        ret[i] = make([]uint8, dx)
        for j := 0; j < dx; j++ {
            ret[i][j] = uint8((i + j))
        }
    }
    return ret
}

func main() {
    pic.Show(Pic)
}

Maps

package main

import (
    "strings"
    "golang.org/x/tour/wc"
)

func WordCount(s string) map[string]int {
    m := make(map[string]int)
    for _, v := range strings.Fields(s) {
        m[v] += 1
    }
    return m
}

func main() {
    wc.Test(WordCount)
}

Fibonacci closure

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
    v1 := 1
    v2 := 1
    return func() int {
        v1, v2 = v2, v1 + v2
        return v1
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

Stringers

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ipAddr IPAddr) String() string {
    return fmt.Sprintf("%v.%v.%v.%v", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]) 
}

func main() {
    addrs := map[string]IPAddr{
        "loopback":  {127, 0, 0, 1},
        "googleDNS": {8, 8, 8, 8},
    }
    for n, a := range addrs {
        fmt.Printf("%v: %v\n", n, a)
    }
}

Errors

package main

import (
    "fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
    return fmt.Sprint("cannot Sqrt negative number: %f\n",
                      float64(e))
}

func Sqrt(x float64) (float64, error) {
    if x < 0.0 {
        return 0, ErrNegativeSqrt(x)
    }
    z := 10000.0
    for i := 0; i < 10; i++ {
        z = z - (z * z - x) / (2 * z)
        fmt.Println(z)
    }
    return z, nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}

Reader

package main

import (
    "strings"
    "golang.org/x/tour/reader"
)

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (mr MyReader) Read(b []byte) (int, error) {
    r := strings.NewReader("A")
    return r.Read(b)
}

func main() {
    reader.Validate(MyReader{})
}

rot13Reader

package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

func rot13(b byte) byte {
    if 'A' <= b && b <= 'Z' {
        k := b - 'A'
        return 'A' + (k-13+26)%26
    }
    if 'a' <= b && b <= 'z' {
        k := b - 'a'
        return 'a' + (k-13+26)%26
    }
    return b
}

func (rr rot13Reader) Read(b []byte) (int, error) {
    n, e := rr.r.Read(b)
    if e == io.EOF {
        return n, e
    }
    for i := 0; i < n; i++ {
        b[i] = rot13(b[i])
    }
    return n, e
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

では良いインプットと良いプログラミングを。

2016年2月21日日曜日

Webサービスにおける暗号化の基本 / SSL,https

mainpic

これまでの話で通信に関する暗号化の基礎的な要素の理解を進めてきました。

共通鍵と公開鍵という仕組み存在し、デジタル署名を行うことが可能でした。さらに、公開鍵の所有者を第三者が認める電子証明書があり、第三者機関を認証局と呼んでいましたね。

一番の脅威である「なりすまし」を認証局の仕組みを利用して防ぎ、デジタル署名を利用し「改ざん」が検知出来るようになり、鍵の仕組みをきちんと運用することにより「盗聴」されないように暗号化され、電子証明書から「否認」を防止できました。

さて、最後となる今回はこれらの技術を掛けあわせて動いているSSL: Secure Sockets Layerの仕組みをまとめていきます。


Webサービス開発における暗号化の基本

第四回目の今回は、

  1. 共通鍵暗号化方式
  2. 公開鍵暗号化方式
  3. デジタル署名(電子署名)
  4. 電子証明書
  5. 認証局
  6. オレオレ証明書
  7. SSLの仕組み(今日はここまで)

最後はSSL(https)の仕組みに関してです。


SSL: Secure Sockets Layer

SSLの仕組みの説明の前に、用語の整理を行います。

  • SSL
    TCP/IPネットワークで、データを暗号化して送受信するプロトコル。OSI参照モデルではセッション層に位置している。

  • https
    SSLによって提供されるセキュアな接続の上でアプリケーション層のhttp通信を行うこと。

  • TLS: Transport Layer Security
    SSLはNetscape Communicationsが開発したが、後にSSL3.0を元にTLS1.0としてRFCに定められた。特に区別する場合を除きSSLと同等のものである。

実はhttpsというプロトコルは存在せず、HTTP over SSL/TLSという二つのプロトコルを組み合わせたものをhttpsと呼んでいます。

SSLの仕組み

要約すると、クライアントがサーバーの電子証明書から公開鍵を取り出して共通鍵を暗号化しサーバーに返送、サーバーは秘密鍵を使って復号化し、その後は共通鍵暗号化方式で通信を行うというもの。

少し細かく見ていきましょう。

Created with Raphaël 2.1.2WebサーバーWebサーバークライアントクライアント認証局から電子証明書を発行してもらう。サーバー内に秘密鍵とセットで保持。サーバー秘密鍵電子証明書SSL接続要求電子証明書を送信認証局の電子証明書を確認(ルート証明書などを利用)認証局の公開鍵で復号化し電子証明書から公開鍵を取得サーバー公開鍵共通鍵を生成 *サーバー公開鍵を使って暗号化サーバー公開鍵暗号化された共通鍵共通鍵を送信秘密鍵を使って復号化サーバー秘密鍵電子証明書共通鍵共通鍵を使って通信共通鍵を使って通信

クライアントが共通鍵を生成する箇所がありますが、正確には共通鍵の基となるプリマスターシークレットというものを生成します。サーバーは受け取ったプリマスターシークレットを元にマスターシークレットを生成し、共通鍵を得ます。

仕組みは以上です。

実際にどのように組み込んでいるのか、シンプルなオレオレhttpsサーバーを構築してみました。

オレオレは「自己署名証明書」を利用することを意味していますw


nodejsによるSSL通信

まずはWebサービスにおける暗号化の基本 / 認証局でも作ったように、鍵、署名リクエスト、自己署名証明書を作成します。

openssl genrsa 2048 > my.key # 鍵
openssl req -new -key my.key > my.csr # 署名リクエスト
openssl x509 -days 3650 -req -signkey my.key < my.csr > my.crt # 自己署名証明書

これを使って、nodejsでサーバーを作るのは以下のようなソースになります。

var https = require('https');
var fs = require('fs');
var options = {
  key: fs.readFileSync('./my.key'),
  cert: fs.readFileSync('./my.crt')
};
https.createServer(options, function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
 }).listen(1338, '127.0.0.1');
 console.log('Server running at https://127.0.0.1:1338/');

my.keyとmy.crtを利用していますね。上記フロー図のWebサーバーのどこで利用されるのかが検討がつくかと思います。

これをsslserver.jsと名づけて保存し、

node sslserver.js

とすれば、https://127.0.0.1:1338でhttps通信することが出来ます。

ただし、自己署名証明書を利用しており、正しいCAを通過していないので以下のように赤くなり、信頼できませんといわれます。
https

以上がsslの仕組みとサンプルサーバーになります。

では、良いインプットと良いプログラミングを。