30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

『Real World HTTP』を読んだ

欲しいものリストから送って頂き、読んでいた。ようやく読了。

Webを支えるプロトコルであるHTTPについて、メソッドとはというところから始まり、HTTP/2に関する話題まで、幅広く網羅している。
主にクライアント側の視点で書かれている。

O'Reilly Japan - Real World HTTP

Go言語でサンプルを実装したり、curlを使って実際にどんなレスポンスが返ってくるか試したりしながら、進んでいく。

Goでのサンプルは実際に写経しながら進めた。その成果が下記のリポジトリ
全部やったわけではないが、TLS、チャンク、プロトコルアップグレード、OAuth2などを実装できたのはよかった。
curlについても少しは使えるようになった。どんなレスポンスが返ってくるのか見れるので、覚えると面白いと思う。

Webに関する基本的な知識を広げたくて、本書を読んだ。

JavaScriptでプログラミングの世界に入り、就職後も、SPAの保守や開発を行っている。
そのためサーバーやインフラの知識はすごく浅い。極端に言えば、用意してもらったAPIとやり取りしながらビューの構築を繰り返しているだけ。

職場の人たちがまさにそうなのだが、ちゃんとしたプログラマは、自分の専門外についてもそれなりに知識を持っている。
詳しくはなくても一通り理解しており、どんな話題であっても対応できる。
そして、詳しくはなくても概要は掴んでいるし理解があるから、必要に応じて調べたりキャッチアップできたりする。 自分にはこの、「基礎教養」みたいものが足りない。
この状況を何とかしたいと思っている。

「あとがき」に「最新のさまざまなウェブのトピックにキャッチアップできる基礎力が身につく本としてまとめました」とある本書は、上記のような問題意識を持っている自分にはちょうどよかった。
「HTTPに関する内容を一冊に集めることを目的」(「まえがき」)としているため、各トピックの内容に深く踏み込むことはないのだが、取り敢えずどんな用語や技術があるのかは分かった。
相変わらず「何となく知っている」という状態だが、その範囲や深さは広がったはず。気になったことがあったら本書を参照して、必要ならその内容を取っ掛かりにして自分で調べていけばいい。

ただ、全くの初心者には厳しい内容だと思う。
一応基礎の説明から入っているのだが、かなり駆け足で、それなりに知識がある読者を前提にしている。
正直なところ、自分もキツイ部分はあった。動画に関する部分は全く知識がなかったし、トランスポート層について理解している前提で話が進むのも厳しかった。
初心者ならまずは、『Webを支える技術』『Web API: The Good Parts』を読んだほうがいいと思う。

TLSについてのメモ

関心が強い部分なので、メモを記録しておく。

  • TLSはHTTPに依存しない。様々な形式のデータを双方向に流せる。
  • 既存のプロトコルに通信経路の安全性をプラスした新たなプロトコルを作り出せる汎用的な仕組み
  • 共通鍵は、送受信者が同じ鍵を用いる。同じ鍵を使って、暗号化と復号化を行う。だから鍵を共有している必要がある。
  • 公開鍵方式は、公開鍵と秘密鍵がある。公開鍵で暗号化した情報を送信。受信者が、秘密鍵を使って復元する。
  • TLSでの暗号化について
    • 通信ごとに一度だけ使う共通鍵を生成
    • 公開鍵方式で、共通鍵を渡す
    • その後は、共通鍵でやり取りする
  • TLSには3つのステップがある。ハンドシェイクプロトコル、レコードプロトコル、再接続時の高速なハンドシェイクプロトコル
  • ブラウザはサーバーから、SSLサーバー証明書を取得する。そこからスタートする。
    • SSLサーバー証明書
      • X.509というフォーマットで書かれており、RFCで仕様が策定されている。
      • 主体者、発行者、公開鍵、有効期限などが書かれている。
      • 証明書には発行者のデジタル署名がある。その発行者の証明書を取得することで、署名の信頼性を確認できる。さらにその証明書の発行者の証明書を取得する。これを繰り返し、大本を辿っていく。すると必ず、最終的に、発行者と主体者が同一の証明書が出てくる。この場合の発行者を、ルート認証局と呼ぶ。ルート認証局の信頼性を確認できれば、そこから展開されている一連の証明書も保証される。
      • ではどうやってルート認証局の信頼性を確認するのか。OSやブラウザには予め、信頼できる認証局の証明書がリストされている。これと照合することで、確認する。
      • 信頼が確認されなかった、発行者と主体者が同一の証明書のことを、通称「オレオレ証明書」と呼ぶ。
      • この一連の仕組みは、Chromeのアドレスバーの左のアイコンで確認できる。
    • 上記の、公開鍵を保証する仕組みを、公開鍵基盤という
  • 証明書の信頼が保証されたら、共通鍵の交換を行う。これは、鍵交換専用アルゴリズムを使って行われる
    • 鍵交換専用アルゴリズムでは、サーバーとクライアントがそれぞれに鍵の種を生成し、それを交換。それぞれで共通鍵を作る。鍵の種を交換する際に、公開鍵を使う。
    • 通信時の暗号化には、共通鍵とは別の鍵も使う。
  • 通信の高速化のために様々な工夫が行われており、QUICという通信方式もその一種。
  • 「サーバーを認証し、鍵を交換して通信を行う」というのが、TLSの骨格。
    • それは変えずに、アルゴリズムの組み合わせをリスト化してそこからクライアントとサーバーに選ばせることで、柔軟性を持たせている。
    • このアルゴリズムのセットを暗号スイートと呼ぶ
  • ALPNという機能によって、アプリケーション層のプロトコルHTTP/1.1HTTP/2など)を選択できる。ファイルタイプや言語のコンテントネゴシエーションのように、クライアントからリストを送り、サーバーがその中から一つを選んで返す。
  • TLSではプロキシサーバーに干渉されることがないため、クライアントとサーバーの間で調整できれば、互換性のないプロトコルも利用できる。

誤植リスト

誤植が多かった。

正誤表が用意されているのはいいのだが、他にもまだまだある。

見つけたものを全部記録しているわけではないのだが、一部メモしておいたので、書いておく。
誤植を見つけたらオライリーにメールしてくれとのことだったので、しておいた。

例9-5のように、本書の通りに記述しても動かないサンプルがある。

74ページ

RFC3986に準拠した変換

RFC1866に準拠した変換

85ページ

$ curl -H "Content-Type=image/jpeg" -d "@image.jpeg" http://localhost:18888

$ curl -H "Content-Type: image/jpeg" -d "@image.jpeg" http://localhost:18888

171ページ

例6−10が間違っている。

まず、冒頭に書くべきpackage mainがない。
それから、コメントも間違っている。

// handleは変わらないので省略

// handlerは変わらないので省略

174ページ

例6-13のimportが書かれていない。
iotimeといった、これまで出てきていないパッケージを読み込まないと動かないのだから、記載が必須。

import (
  "io"
  "time"
  "fmt"
  "log"
  "net/http"
)

200ページ

4章で紹介したComet

5章で紹介したComet

257ページ

例9−5。このままだとエラーが出て動かない。

this.primes = stream([]);

this.primes = m.stream([]);

279ページ

サーバーからHTTPでの接続を要請したいときには、

サーバーからHTTPSでの接続を要請したいときには、

308ページ

例11−1。

var redirectURL = "https://localhost:18888"

var redirectURL = "http://localhost:18888"

そもそもredirectURLはこのサンプルでは使ってないから、値は何でもいいのだが。
さらに言えば、この変数は不要。

311ページ

例11−3。

"io/ioutil"をimportしておかないと動かない。

Higher-Order Components と Recompose の初歩

Higher-Order Components(以下、HOC)は、Reactのコンポーネントを作る際のパターン。
HOCを使うことで、複数のコンポーネントで使っている処理を共通化したり、SFCにライフサイクルメソッドを追加したりすることが出来る。

基本的な構造

HOCは、以下のような関数を使って実現する。

function hocFactory(WrappedComponent) {
  return class extends React.Component {
    render() {
      return <WrappedComponent />;
    }
  };
}

コンポーネントを引数として受け取り、それに機能を追加した新しいコンポーネントを返す。
上記の例では何もしていないが、hocFactory(ファクトリ関数)のなかで様々な処理を行うことで、WrappedComponentに新しい機能を加えることが出来る。

このエントリでは、HOCを使った以下のテクニックについて説明する。

  • propsをファクトリ関数から受け取る
  • SFCにライフサイクルメソッドを追加する

propsをファクトリ関数から受け取る

propsとしてtextを受け取りそれを表示する以下のようなコンポーネントがあるとする。

function Basic({text}) {
  return <div>{text}</div>;
}

<Basic text="abc" />とすれば、abcと表示される。
同じことをHOCでやる場合、次のようになる。

function hocFactory(WrappedComponent) {
  return class extends React.Component {
    render() {
      return <WrappedComponent text="abc" />;
    }
  };
}
const EnhancedBasic = hocFactory(Basic);

こうすると、<Basic text="abc" /><EnhancedBasic />は同じ内容になり、どちらもabcと表示される。

もちろんこの例の場合、HOCを使う意味はないし、むしろ煩雑になっているだけである。
だが異なるコンポーネントに共通の機能を渡したいケースなどでは、このテクニックが役に立つ。

インライン要素のテキストを表示するTextContentと、リストを表示するListContentという2つのコンポーネントを用意して、それを使いながら説明してく。

HOCを使わない場合

TextContentListContentの内容は次の通り。

function TextContent({text}) {
  return (
    <span>
      {text}
    </span>
  );
}

function ListContent({contents}) {
  return (
    <ul>
      {contents.map(i => <li key={i}>{i}</li>)}
    </ul>
  );
}

以下のようにすることで、それぞれテキストとリストが表示される。

<TextContent text="xyz" />
<ListContent contents={['abc', '123', 'def']} />

このコンポーネントに、以下の機能を追加することになった。

  • 要素をクリックすることで、文字の色が変わる
  • TextContentは、黒⇔赤
  • ListContentは、黒⇔オレンジ

以下が、これを実装したもの。

class TextContent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {color: 'black'};
    this.changeColor = this.changeColor.bind(this);
  }
  changeColor() {
    this.setState({color: this.state.color === 'black' ? 'red' : 'black'});
  }
  render() {
    const {color} = this.state;
    return (
      <span style={{color}} onClick={this.changeColor}>
        {this.props.text}
      </span>
    );
  }
}

class ListContent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {color: 'black'};
    this.changeColor = this.changeColor.bind(this);
  }
  changeColor() {
    this.setState({color: this.state.color === 'black' ? 'orange' : 'black'});
  }
  render() {
    const {color} = this.state;
    return (
      <ul style={{color}} onClick={this.changeColor}>
        {this.props.contents.map(i => <li key={i}>{i}</li>)}
      </ul>
    );
  }
}

機能としては問題ないが、一目見て分かる通り、ほとんど同じ内容が重複して記述してある。
HOCを使うことで、効率よく記述することが可能になる。

HOCを使って書き直す

まず、TextContentListContentをシンプルな形に戻す。
そして、文字の色はprops.colorとして受け取り、クリックによる文字色の変更機能はprops.onClickとして受け取るようにした。

function TextContent({text, color, onClick}) {
  return (
    <span style={{color}} onClick={onClick}>
      {text}
    </span>
  );
}

function ListContent({contents, color, onClick}) {
  return (
    <ul style={{color}} onClick={onClick}>
      {contents.map(i => <li key={i}>{i}</li>)}
    </ul>
  );
}

<Basic />の例で示したように、HOCのファクトリ関数はpropsをコンポーネントに渡すことが出来るのだから、それを利用すればいい。

以下が、HOCを使って実装したバージョン。
共通の処理をファクトリ関数にまとめている。

function hocFactory(WrappedComponent, color1, color2) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {color: color1};
      this.changeColor = this.changeColor.bind(this);
    }
    changeColor() {
      this.setState({color: this.state.color === color1 ? color2 : color1});
    }
    render() {
      return (
        <WrappedComponent
          {...this.props}
          color={this.state.color}
          onClick={this.changeColor}
        />
      );
    }
  };
}

const EnhancedTextContent = hocFactory(TextContent, 'black', 'red');
const EnhancedListContent = hocFactory(ListContent, 'black', 'orange');
<EnhancedTextContent text="xyz" />
<EnhancedListContent contents={['abc', '123', 'def']} />

注意点としては、以下の{...this.props}を忘れないこと。
これを記述しないと、新しく作られたコンポーネントに渡されたprops(この例ではtextcontents)が反映されない。

<WrappedComponent
  {...this.props}
  color={this.state.color}
  onClick={this.changeColor}
/>

また、{...this.props}のあとにpropを定義すると、その内容で上書きされる。
そのため、以下のように定義すると、<EnhancedTextContent text="xyz" />としてもxyzではなくfooと表示される。

<WrappedComponent
  {...this.props}
  text="foo"
  color={this.state.color}
  onClick={this.changeColor}
/>

this.props.children

ちなみにこのケースでは、HOCを使わずthis.props.childrenを使った書き方でも実装できる。

function TextContent({text}) {
  return (
    <div>
      {text}
    </div>
  );
}

function ListContent({contents}) {
  return (
    <ul>
      {contents.map(i => <li key={i}>{i}</li>)}
    </ul>
  );
}

class Wrapper extends React.Component {
  constructor(props) {
    super(props);
    this.state = {color: this.props.color1};
    this.changeColor = this.changeColor.bind(this);
  }
  changeColor() {
    const {color1, color2} = this.props;
    this.setState({color: this.state.color === color1 ? color2 : color1});
  }
  render() {
    const {color} = this.state;
    return (
      <div style={{color}} onClick={this.changeColor}>
        {this.props.children}
      </div>
    );
  }
}
<Wrapper color1="black" color2="red">
  <TextContent text="xyz" />
</Wrapper>
<Wrapper color1="black" color2="orange">
  <ListContent contents={['abc', '123', 'def']} />
</Wrapper>

HOCとどちらを採用すべきかは、状況によるのだと思う。

SFCにライフサイクルメソッドを追加する

Reactのコンポーネントを定義する際は、出来るだけSFC(Stateless Functinal Componenens)で定義するのが望ましいとされる。
SFCはstateを持たないため、コンポーネントをステートレスに保てるからだ。
しかしSFCには、componentDidMountなどのライフサイクルメソッドを利用できないという欠点がある。

しかしHOCを使うことで、SFCでもライフサイクルメソッドを利用できるようになる。

function Basic({text}) {
  return <div>{text}</div>;
}

function hocFactory(WrappedComponent) {
  return class extends React.Component {
    componentDidMount() {
      console.log('componentDidMount');
    }
    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

const EnhancedBasic = hocFactory(Basic);

こうすると、EnhancedBasicをマウントしたときにログが表示されるようになる。

recompose を使う

HOCによるライフサイクルメソッドの追加は、Recomposeというライブラリを使うことによって、より簡単に記述できる。

https://github.com/acdlite/recompose

先程のEnhancedBasicを定義する場合、次のように書く。

import React from 'react';
import {lifecycle} from 'recompose';

function Basic({text}) {
  return <div>{text}</div>;
}

const EnhancedBasic = lifecycle({
  componentDidMount() {
    console.log('componentDidMount');
  },
})(Basic);

lifecycleのなかでsetStateを使うことも出来る。
そしてstateは、元のコンポーネントにpropsとして渡される。

そのため以下のように書くと、componentWillMountcomponentDidMountの時間差を表示するコンポーネントが作られる。

function Basic({time}) {
  return <div>{time}</div>;
}

const EnhancedBasic = lifecycle({
  componentWillMount() {
    this.setState({time: new Date().getTime()});
  },
  componentDidMount() {
    this.setState({time: new Date().getTime() - this.state.time});
  },
})(Basic);

なおRecomposeには、lifecycle以外にもHOCを便利に使うための様々な機能が用意されている。
https://github.com/acdlite/recompose/blob/master/docs/API.md

参考資料