PCがあれば何でもできる!

へっぽこアラサープログラマーが、覚えたての知識を得意げにお届けします

Let's Encryptが急に動かなくなった件(THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE.)

Let's EncryptでSSL化したサイトに、もうすぐ証明書の期限切れが来るというメールが。

cronで期限の更新も自動化したはずなのに…。

ということで調べてみると、以下のようなエラーが発生している。

./certbot-auto certonly --webroot -w /usr/share/nginx/html -d www.xxx.com --renew-by-default
〜〜〜 省略 〜〜〜
Creating virtual environment...
Installing Python packages...
Had a problem while installing Python packages:
〜〜〜 省略 〜〜〜
Collecting certbot==0.8.1 (from -r /tmp/tmp.lHgsdDzKb3/letsencrypt-auto-requirements.txt (line 187))
  Downloading certbot-0.8.1-py2-none-any.whl (217kB)
Collecting certbot-apache==0.8.1 (from -r /tmp/tmp.lHgsdDzKb3/letsencrypt-auto-requirements.txt (line 190))
  Downloading certbot_apache-0.8.1-py2-none-any.whl (103kB)
Requirement already satisfied (use --upgrade to upgrade): setuptools>=1.0 in /root/.local/share/letsencrypt/lib/python2.7/site-packages (from cryptography==1.2.3->-r /tmp/tmp.lHgsdDzKb3/letsencrypt-auto-requirements.txt (line 35))
THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE. If you have updated the package versions, please update the hashes. Otherwise, examine the package contents carefully; someone may have tampered with them.
    pycparser==2.14 from https://pypi.python.org/packages/74/0e/111a4349e81e2a9846129e0357e154b496559799ec34a6b27bc677247bfa/pycparser-2.14-py2.py3-none-any.whl#md5=130e8dc5b640d9339ee4056da0cdc73a (from -r /tmp/tmp.lHgsdDzKb3/letsencrypt-auto-requirements.txt (line 11)):
        Expected sha256 7959b4a74abdc27b312fed1c21e6caf9309ce0b29ea86b591fd2e99ecdf27f73
             Got        52bcedd9180999fc7f3128b4b89ce638ffc0ffcbd136873379d5a37e4f9e7932

You are using pip version 8.0.3, however version 8.1.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

どうやらPythonパッケージのpycparserのハッシュが不一致となり、エラーになってしまう様子。

下記の記事で、同様の現象が発生しているとの報告が多々あり、解決法まで記載されました。

こちらが10/3からの記事で、私がこの問題に直面したのも10/3なので、偶然にもリアルタイムで体験してしまったようです^^;

解決法をまとめると、certbot-autoのスクリプトを以下のように修正。

# 修正前
pyparser==2.14 \
--hash=sha256:7959b4a74abdc27b312fed1c21e6caf9309ce0b29ea86b591fd2e99ecdf27f7

# 修正後
pyparser==2.14 \
--hash=sha256:7959b4a74abdc27b312fed1c21e6caf9309ce0b29ea86b591fd2e99ecdf27f73 \
--no-binary pycparser

pythonに詳しくないので、修正内容の詳細まではご説明できません!

【TypeScript】express4.xとangular1.5間で、オブジェクトをシリアライズ化して送受信

node + express4.xでREST APIを作り、angular1.5から利用する際、 お互いにjavascriptなのに、送受信の度にjsonで表現できないオブジェクトを、元の状態に戻すのがめんどくさい。

例えばmoment.jsを利用して日付を送る場合、

const reqData = {
  date: moment([2016,1,1]),
};

↑のようなオブジェクトを送ろうと思っても、jsonでは

{
  "date": "2016-01-01T00:00:000"
}

としか表現できない。なのでjsonを受け取った側でmomentのオブジェクトに戻したければ、

resData.date = moment(resData.date);

こんな感じにしないといけない。

これが、

  • ブラウザのリクエストをサーバーが受け取った際
  • サーバーのレスポンスをブラウザが受け取った際

の2箇所で発生するため、サーバー側とブラウザ側に同じような処理を書いているうちに、だんだん馬鹿らしくなってくる。

そこで、うまく通信をシリアライズ化して、面倒を減らせないかなという目論見のもと、↓のようなコードを書いてみた。

// serializer.ts

'use strict';
import * as _ from 'lodash';
import * as moment from 'moment';

/** シリアライズ済オブジェクトインターフェース */
interface SerializedObject {
  $serialize: {
    name: string;
    value: any;
  }
}

/** 変換定義インターフェース */
interface Definition {
  /** シリアライズ化対象のオブジェクトか判定 */
  target: (v: any) => boolean;
  /** シリアライズ化処理 */
  serialize: (v) => string;
  /** デシリアライズ化処理 */
  deserialize: (v: string) => any;
}

/** 変換定義:ここに定義した型のみ変換 */
const definitions: {[name: string]: Definition} = {
  moment: {
    target: (v: moment.Moment) => { return moment.isMoment(v); },
    serialize: (v: moment.Moment) => { return v.toISOString(); },
    deserialize: (v: string) => { return moment(v); },
  },
  Date: {
    target: (v: Date) => { return v instanceof Date; },
    serialize: (v: Date) => { return v.toISOString(); },
    deserialize: (v: string) => { return new Date(v); },
  }
};

/** JSON.stringify用のreplacer */
export function replacer(key: string, obj: any) {
  if (typeof obj === 'undefined' || obj === null) {
    return obj;
  }

  if (Array.isArray(obj)) {
    return (<any[]>obj).map((v) => {
      return replaceValue(v);
    });
  }

  if (typeof obj === 'object') {
    return _.fromPairs(Object.keys(obj).map((k) => {
      return [k, replaceValue(obj[k])];
    }));
  }

  return obj;
}

function replaceValue(v: any) {
  for (let name of Object.keys(definitions)) {
    const d = definitions[name];
    if (d.target(v)) {
      const sobj: SerializedObject = <any>{};
      sobj.$serialize = { name, value: d.serialize(v) };
      return sobj;
    }
  }
  return v;
}

/** JSON.parse用のreviver */
export function reviver(key: string, obj: any) {
  if (typeof obj === 'undefined' || obj === null) {
    return obj;
  }

  const sobj = <SerializedObject>obj;
  if (sobj.$serialize) {
    return definitions[sobj.$serialize.name].deserialize(sobj.$serialize.value);
  }
  return obj;
}

/** オブジェクトをシリアライズ化 */
export function serialize(obj: any) {
  return JSON.stringify(obj, replacer);
};

/** jsonをデシリアライズ化 */
export function deserialize(json: string) {
  return JSON.parse(json, reviver);
}

やっていることとしては、definitionsに定義された、オブジェクトが見つかると、特殊な記法に変換する。

例えば

import * as serializer from './serializer';

const reqData = {
  date: moment([2016,1,1]),
  number: 1
};

console.log(serializer.serialize(reqData));

だと、こんな感じの出力になる。

{
  "date": {
    "$serialize": {
      "name": "moment",
      "value": "2016-01-01T00:00:000Z"
    }
  },
  "number": 1
}

後は、$serializeというキーを探して、定義をもとに変換しなおしているという流れ。

ここまでできたら、expressとangularそれぞれに、このシリアライズ処理をやってもらうだけ。

angular側はこんな感じ。 transformRequestとtransformResponseを使って、シリアライズ処理を入れる。 毎回指定するのが面倒なら、defaultのtransformに設定してもいいけど、他サーバーとの通信も考えると、ラップするような形がいいかも。

$http.post<any>(url, data, {
  transformRequest: (d: any) => { return serializer.serialize(d); },
  transformResponse: (d: any) => { return serializer.deserialize(d); },
  responseType: 'text',
}).then();

express側はこんな感じ。 json変換時のreplacerとreviverを指定できる。

const app = express();

// レスポンスのjson化のreplacerを指定
app.set('json replacer', serializer.replacer);

// リクエストをjson復元のreviverを指定
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json({ reviver: serializer.reviver }));

これで後は、definitionsに書いているオブジェクトに関しては、何も考えないで受け渡しができる。

deepなクローンでもない、シンプルな処理なので、重くはないはず。

【TypeScript】日本の祝日判定

日本の祝日を求めるのに、良いライブラリがないか探していた所、Osamu Takeuchi氏のjapanese-holidays-jsを発見。

GitHub - osamutake/japanese-holidays-js: Provides utilities to manipulate japanese holidays.

元ソースは、あまり自分に馴染みのないCoffeeScriptで書かれていて、TypeScriptで使うのに、型定義作ったりラップしたりでも良かったのですが、ソース理解や今後弄りたくなることも考慮して、TypeScriptに焼き変えてみました。

参考程度にどうぞ。

'use strict';

/**
 * japanese-holidays-js
 * 日本の休日を JavaScript で計算するためのライブラリです。
 * https://github.com/osamutake/japanese-holidays-js
 * 
 * ↑のライブラリを、coffeescriptからtypescriptに焼き変え
 */

// 元の時刻から指定時間だけずらした時刻を生成して返す
const shiftDate = (date: Date, year: number, mon: number, day: number, hour?: number, min?: number, sec?: number, msec?: number)  => {
  const res = new Date(2000,0,1);
  res.setTime(date.getTime() + ((((day || 0) * 24 + (hour || 0)) * 60 + (min || 0)) * 60 + (sec || 0)) * 1000 + (msec || 0));
  res.setFullYear(res.getFullYear() + (year || 0) + Math.floor((res.getMonth() + (mon || 0)) / 12));
  res.setMonth(((res.getMonth() + (mon || 0)) % 12 + 12) % 12);
  return res;
};

const u2j = (d: Date) => { return shiftDate(d,0,0,0,+9); };
const j2u = (d: Date) => { return shiftDate(d,0,0,0,-9); };
const uDate = (y: number, m: number, d: number) => { return new Date(Date.UTC(y,m,d)); };
const jDate = (y: number, m: number, d: number) => { return j2u(uDate(y,m,d)); };
const getJDay = (d: Date) => { return (u2j(d)).getUTCDay(); };
const getJDate = (d: Date) => { return (u2j(d)).getUTCDate(); };
const getJMonth = (d: Date) => { return (u2j(d)).getUTCMonth(); };
const getJFullYear = (d: Date) => { return (u2j(d)).getUTCFullYear(); };
const getJHours = (d: Date) => { return (u2j(d)).getUTCHours(); };
const getJMinutes = (d: Date) => { return (u2j(d)).getUTCMinutes(); };

/**
 * ヘルパ関数
 */
// 年を与えると指定の祝日を返す関数を作成
const simpleHoliday = (month: number, day: number) => {
  return (year: number) => jDate(year, month-1, day);
};
// 年を与えると指定の月の nth 月曜を返す関数を作成
const happyMonday = (month: number, nth: number) => {
  return (year: number) => {
    const monday = 1
    const first = jDate(year, month-1, 1);
    const day = (7 - (getJDay(first) - monday)) % 7 + (nth - 1) * 7;
    return shiftDate(first, 0, 0, day);
  };
};
// 年を与えると春分の日を返す
const shunbunWithTime = (year: number) => {
  return new Date(-655866700000 + 31556940400 * (year-1949) );
};
const shunbun = (year: number) => {
  const date = shunbunWithTime(year);
  return jDate(year, getJMonth(date), getJDate(date));
}
// 年を与えると秋分の日を返す
const shubunWithTime = (year: number) => {
  const day = { 1603: 23, 2074: 23, 2355: 23, 2384: 22 }[year];
  if (day) { return jDate(year, 9-1, day); }
  return new Date(-671316910000 + 31556910000 * (year-1948));
}
const shubun = (year: number) => {
  const date = shubunWithTime(year);
  return jDate(year, getJMonth(date), getJDate(date));
}

/**
 * 休日定義
 * https://ja.wikipedia.org/wiki/%E5%9B%BD%E6%B0%91%E3%81%AE%E7%A5%9D%E6%97%A5
 */
const definition = [
  ['元日', simpleHoliday(1, 1), 1949],
  ['成人の日', simpleHoliday(1, 15), 1949, 1999],
  ['成人の日', happyMonday(1, 2), 2000],
  ['建国記念の日', simpleHoliday(2, 11), 1967],
  ['昭和天皇の大喪の礼', simpleHoliday(2, 24), 1989, 1989],
  ['春分の日', shunbun, 1949],
  ['皇太子明仁親王の結婚の儀', simpleHoliday(4, 10), 1959, 1959],
  ['天皇誕生日', simpleHoliday(4, 29), 1949, 1988],
  ['みどりの日', simpleHoliday(4, 29), 1989, 2006],
  ['昭和の日', simpleHoliday(4, 29), 2007],
  ['憲法記念日', simpleHoliday(5, 3), 1949],
  ['みどりの日', simpleHoliday(5, 4), 2007],
  ['こどもの日', simpleHoliday(5, 5), 1949],
  ['皇太子徳仁親王の結婚の儀', simpleHoliday(6, 9), 1993, 1993],
  ['海の日', simpleHoliday(7, 20), 1996, 2002],
  ['海の日', happyMonday(7, 3), 2003],
  ['山の日', simpleHoliday(8, 11), 2016],
  ['敬老の日', simpleHoliday(9, 15), 1966, 2002],
  ['敬老の日', happyMonday(9, 3), 2003],
  ['秋分の日', shubun, 1948],
  ['体育の日', simpleHoliday(10, 10), 1966, 1999],
  ['体育の日', happyMonday(10, 2), 2000],
  ['文化の日', simpleHoliday(11, 3), 1948],
  ['即位礼正殿の儀', simpleHoliday(11, 12), 1990, 1990],
  ['勤労感謝の日',simpleHoliday(11, 23), 1948],
  ['天皇誕生日', simpleHoliday(12, 23), 1989],
]

/**
 * 休日を与えるとその振替休日を返す
 * 振り替え休日がなければ null を返す
 */
const furikaeHoliday = (holiday: Date) => {
  // 振替休日制度制定前 または 日曜日でない場合 振り替え無し
  const sunday = 0
  if (holiday < jDate(1973, 4-1, 30-1) || getJDay(holiday) != sunday) { return null; }

  // 日曜日なので一日ずらす
  let furikae = shiftDate(holiday, 0, 0, 1);
  
  // ずらした月曜日が休日でなければ振替休日
  if (!isHolidayAt(furikae, false)) { return furikae; }

  // 旧振り替え制度では1日以上ずらさない
  if (holiday < jDate(2007, 1-1, 1)) {
    return null; // たぶんこれに該当する日はないはず?
  }

  // 振り替えた結果が休日だったら1日ずつずらす
  while(true) {
    furikae = shiftDate(furikae, 0, 0, 1);
    if (!isHolidayAt(furikae, false)) { return furikae; }
  }
}

/**
 * 休日を与えると、翌日が国民の休日かどうかを判定して、
 * 国民の休日であればその日を返す
 */
const kokuminHoliday = (holiday: Date) => {
  // 制定前
  if (getJFullYear(holiday) < 1988) { return null; }

  // 2日後が振り替え以外の祝日か
  if (!isHolidayAt(shiftDate(holiday, 0, 0, 2), false)) { return null; }

  const sunday = 0
  const monday = 1
  const kokumin = shiftDate(holiday, 0, 0, 1);
  if (isHolidayAt(kokumin, false) || // 次の日が祝日
      getJDay(kokumin)==sunday ||  // 次の日が日曜
      getJDay(kokumin)==monday     // 次の日が月曜(振替休日になる)
  ) { return null; }
  return kokumin;
}

/**
 * 計算結果をキャッシュする
 *
 * holidays[furikae] = {
 *   1999:
 *     "1,1": "元旦"
 *     "1,15": "成人の日"
 *     ...
 * }
 */
interface Holidays {
  [is_furikae: string]: { [y: number]: ({[month_day: string]: string}) };
}
const holidays: Holidays = { true: {}, false: {} }
const getHolidaysOf = (y: number, is_furikae = true) => {
  // キャッシュされていればそれを返す
  const furikae = is_furikae ? 'true' : 'false';
  const cache = holidays[furikae][y];
  if (cache) { return cache; }
  
  /**
   * されてなければ計算してキャッシュ
   * 振替休日を計算するには振替休日以外の休日が計算されて
   * いないとダメなので、先に計算する
   */
  const wo_furikae: {[month_day: string]: string} = {}
  definition.forEach((entry) => {
    const entry0 = <string>entry[0];
    const entry1 = <(y: number) => Date>entry[1];
    const entry2 = <number>entry[2];
    const entry3 = <number>entry[3];
    if (entry2 && y < entry2) { return; }   // 制定年以前
    if (entry3 && entry3 < y) { return; }   // 廃止年以降
    const holiday = entry1(y);                // 休日を計算
    if (!holiday) { return; }               // 無効であれば無視
    const m = getJMonth(holiday) + 1;         // 結果を登録
    const d = getJDate(holiday);
    wo_furikae[`${m},${d}`] = entry0;
  });
      
  holidays['false'][y] = wo_furikae;
  
  // 国民の休日を追加する
  const kokuminHolidays = []
  Object.keys(wo_furikae).forEach((month_day) => {
    const [month, day] = month_day.split(',').map((v) => { return parseInt(v); });
    const holiday = kokuminHoliday(jDate(y, month-1, day));
    if (!holiday) { return; }
    const m = getJMonth(holiday) + 1; //結果を登録
    const d = getJDate(holiday);
    kokuminHolidays.push(`${m},${d}`);
  });
  kokuminHolidays.forEach((holiday) => {
    wo_furikae[holiday] = '国民の休日';
  });
  
  // 振替休日を追加する
  const w_furikae: {[month_day: string]: string} = {};
  Object.keys(wo_furikae).forEach((month_day) => {
    const name = wo_furikae[month_day];
    w_furikae[month_day] = name;
    const [month, day] = month_day.split(',').map((v) => { return parseInt(v); });
    const holiday = kokuminHoliday(jDate(y, month-1, day));
    if (!holiday) { return; }
    const m = getJMonth(holiday) + 1; // 結果を登録
    const d = getJDate(holiday);
    w_furikae[`${m},${d}`] = '振替休日';
  });
  holidays['true'][y] = w_furikae; // 結果を登録

  return holidays[furikae][y];
}


const isHoliday = (date: Date, is_furikae?: boolean) => {
  return getHolidaysOf(date.getFullYear(), is_furikae)[`${date.getMonth()+1},${date.getDate()}`];
}
const isHolidayAt = (date: Date, is_furikae?: boolean) => {
  return getHolidaysOf(getJFullYear(date), is_furikae)[`${getJMonth(date)+1},${getJDate(date)}`];
}


/**
 * クラス定義
 */
export default {
  getHolidaysOf: (y: number, is_furikae?: boolean) => {
    // データを整形する
    const result: { month: number, day: number, name: string }[] = [];
    const holidays = getHolidaysOf(y, is_furikae);
    Object.keys(holidays).forEach((month_day) => {
      const name = holidays[month_day];
      const [month, day] = month_day.split(',').map((v) => { return parseInt(v); });
      result.push({month, day, name});
    });
        
    // 日付順に並べ直す
    return result.sort((a,b) => { return (a.month-b.month) || (a.day-b.day); });
  },
  isHoliday,
  isHolidayAt,
  shiftDate,
  u2j,
  j2u,
  jDate,
  uDate,
  getJDay,
  getJDate,
  getJMonth,
  getJFullYear,
  getJHours,
  getJMinutes,
  __forTest: {
    shunbunWithTime: shunbunWithTime,
    shubunWithTime: shubunWithTime,
  }
};

LINE Outの無料化はそんなにひどいものだったのか?

f:id:m_doi2:20160417042039p:plain:w150

LINE Outの無料化が、被災地の電話回線の輻輳を引き起こす可能性がある。

この話題が、炎上しながら一気に広まり、結果的に短い時間で多くの人に伝わって良かったなぁと思っています。

ただ当件に関して、Twitterやはてぶの意見を見ると、「偽善者」「売名行為」と、批判を通り越してもはや中傷…。

では果たして、LINE Outの無料化は、話題になっている程ひどいものだったのでしょうか?

私の結論から先にお伝えすると…。

確かに、電話回線が圧迫する可能性があるものを、ばんばん使っても良いように伝わってしまったのは、やはりまずかったと思います。

しかし、LINE Outの無料化自体は、とても素晴らしい判断だったと思います。

理由は、以下の3つのメリットがあるためです。

無料化のメリット

メリット1. 被災地発信ならば、被災地の電話回線を圧迫しない

LINE Outから固定電話や携帯電話にかける場合は、発信側がインターネット回線で、受信側が電話回線を利用することとなります。

これは、LINE Outで被災地にかけてしまうと、話題の通り被災地の輻輳を引き起こします。

f:id:m_doi2:20160417054704p:plain

しかし、逆に被災地から外にかけると、どうでしょう?

この場合は、被災地の電話回線に負荷はかかりません。

f:id:m_doi2:20160417054813p:plain

つまり、被災地の電話回線が輻輳状態で、緊急連絡すらままならない状況でも、通話が可能ということです。

とはいえ、今度はインターネットの帯域を専有したり、他に未知のボトルネックがあったりというリスクはありますので、やはり緊急時に限定した方が良いことは確かでしょう。

メリット2. 金銭面の圧迫を防ぐ

これは説明不要だと思います。

メリット3. 決済手続きが不要となった

LINE Outを使ったことのある方はご存知かと思いますが、LINE Outの通話料は前払い制。

LINE Outを使う前に、コールクレジット(前払い金)の購入の必要があり、もし月額プランとするなら、プランの選択や、クレジットカード登録等の決済設定までしないといけません。

この前払いの仕組みを理解し、決済操作を行うというのは、知らない人にはかなりの負担ですし、そもそも仕組みを理解していても、電池残量を節約したい状況でやりたい操作ではありません。

そういった状況だったのが、無料化したことによって、決済操作を完全にスキップして、いきなり通話可能となったのです。

ではLINE Outを利用すると良いパターンは?

まとめると、

  • 被災地から連絡したい
  • 相手が携帯電話か固定電話以外の連絡手段を持たない
  • 緊急連絡したい

の3つの条件が揃った場合になります。

この条件に合わない場合は、一般的な対応を行うのが良いと思います。

また、パケット通信費を考慮すると、Wi-Fiに繋がる、またはパケット定額に入っているも含めて検討する必要があります。

Wi-Fiは、ドコモ、ソフトバンク、AUが、現在00000JAPANというWi-Fiを無料開放していますので、そちらを利用すると良いと思います。

00000JAPAN とは 大規模災害時にWi-Fi無料開放、設定方法は?

00000JAPANポータル  |  無線LANビジネス推進連絡会【WiBiz(ワイビズ)】無線LANビジネス推進連絡会【WiBiz(ワイビズ)】

もしかするとLINE Outが大活躍したかもしれない

今回は、被災直後に目立った輻輳は起きていませんでした。

そのせいもあり、輻輳を引き起こすという理由で、LINE Outが一方的に悪になってしまいました。

しかし、もし既に輻輳が起きてしまっていて、緊急連絡すらできない状況になっていた場合を想像してみてください。

その場合、LINE Outで被災地から発信することで、緊急連絡をできた可能性がありました。

さらに電話回線に負荷をかけないため、輻輳も少なからず軽減したと思われます。

(さらに言えば、被災地が輻輳中の場合、今回問題になった被災地へのLINE Outは、かなり繋がりにくく、実質大きな問題にはならなかったと思われる)

少し都合よく考え過ぎかもしれませんが、可能性は低くないと思います。

そう考えると、被災直後に無料化したLINEのスピード感は、やはり素晴らしかったのではないでしょうか。

なぜ10分間という制限を設けたか

無料で長時間使わせたくない、そういった理由があるのかはわかりませんが、他にも重要な効果があります。

インターネット回線や、LINE Outのサーバーのリソースも有限なのです。

使いすぎれば結局、電話回線と同じように混線する可能性があります。

そういう意味で10分という制限は、長すぎず短すぎず、バランスのとれた時間ではないかと思います。

なぜ被災地だけ無料化しなかったのか

確かに、被災地だけ無料化すれば、外から被災地にかけてしまうリスクも低く、今回のような問題には発展しなかったと思います。

しかし、そうしなかったのはなぜでしょうか。

想像の範囲になってしまいますが、2つ理由が考えられます。

1. 利用者の地区情報を、アプリで取得するのは難しい

利用者の地区情報を取得するには、事前に自己申告で登録してもらうか、GPSを利用して取得するのが一般的です。

しかし、自己申告ではそもそも意味がありません。わざわざ被災地でGPSをONにして…、なんて手順も複雑ですし、GPS自体が安定性の高いものではないため、プログラムしにくいのも事実です。

2. 決済の仕組みを変更するのに時間がかかる

LINEは恐らく、日本の地域毎に決済額を変えるような仕組みは設けていないと思われます。今回のような例外がない限り何もメリットを感じません。

そのため、被災地のみ無料にしようとすると、決して小規模ではない仕様変更となります。

プログラムを組んだことがない人にはイメージがつきにくいかもしれませんが、大規模なシステムの仕様変更は、非常に繊細な作業で時間がかかります。

例えると、ジェンガの終盤。ちょっとした油断で全てが壊れてしまいます。壊さないようにじっくりやるのがコツです。

そんな作業を、1分1秒が惜しいこのタイミングでやりたくはありません。

デメリットがあっても、全体を無料化したのは、こういった理由からではないかと考えられます。

最後に

今更ですが、私はLINEの社員でもないですし、LINEに何の思い入れもありません。

ただ、LINEは今回の無料化にあたり、決して少なくないコストがかかっていますし、システムに普段以上の負荷がかかることから、通常以上の技術者が長時間配置されているはずです。

このように前線で支援している企業や技術者を、簡単に中傷してしまう状況は、一人の技術者として非常に悲しいことだと感じました。

まだまだ、被災地は大変な状況です。

私も、人として、技術者として、微力ながらできることをしていきます。

リンク

セール中!今ならIIJmioのウェルカムパックが0円から!転入時の手数料や縛り期間等の解説

f:id:m_doi2:20160411103421j:plain:w200

皆さん、格安SIM使ってますか!?

私は、ちょうど1年前にドコモからIIJmioに乗り換えました!

最近は色んな所で言われていますが、やはり安い

月々およそ8,000円1,800円に^^;

電力自由化もそうですが、独占が解消するのは素晴らしいですね!

そしてこの度、妻のドコモの縛りも切れるため、IIJmioにMNP転入しました。

その際、転出・転入費用や縛り期間について改めてまとめましたので、皆さんにも共有できればと思います。

MNP転出時の費用

まず、MNP転出手数料がかかります。

ドコモは2,000円でした。他社はAUが2,000円ソフトバンクが3,000円とのことです。

また、2年縛り等に引っかかる場合は、解約時の違約金がかかります。

そして、最後に見落としがちなのが、月途中解約時の日割り料金

実は、契約満了の翌月1日に解約しても、カケホーダイやパケホーダイ等の定額プランは日割り料金にならない!!当月分全て払わされます。これって2年以上縛ってますよね…、悪どい^^;

ただし裏技として、1日になる前にドコモに連絡し、1日付けでパケホーダイを止める予約をしていれば、パケホーダイ分は払わなくて良くなります。

しかし、基本使用料と絡んだカケホーダイは止められないし、仮にプラン変更してもさらに2年縛りがついてしまうため、素直に全額支払うのが一番良いとのこと。ひどすぎるorz

ちなみに、ソフトバンクは締め日が月末ではない場合があるらしいので要注意です!一度、ショップに確認するのが吉です。

IIJmio転入時の費用

SIM1枚につき、3,240円(税込)の事務手数料がかかります。

これは新規契約でも、MNP転入でも同様です。

ただし、後述のウェルカムパックを購入することで、この事務手数料を格安で済ますことが出来ます。

ウェルカムパックがお得!

ウェルカムパックは、要は、上記の事務手数料 3,240円(税込)の前払いです。

店舗や通販でこのウェルカムパックを購入し、記載のコードを契約時に入力すると事務手数料がタダになります。

このウェルカムパックの定価は、当然3,240円(税込)なのですが、なんと通販等で比較的安く手に入ります。

現在、オススメなのは、セール中の以下の2つです。

1つ目

1つ目がAmazonで販売されているこちら!

IIJ IIJmio SIM 音声通話 パック みおふぉん IM-B043

IIJ IIJmio SIM 音声通話 パック みおふぉん IM-B043

なんと、セール中で324円!(送料350円) 恐らくネット上で最安ではないかと思います。

ただし現在、AmazonでIIJmio SIMカード まとめ買いキャンペーンをやっており、条件次第では、後述の2つ目のセール品の方が0円で手に入る可能性もあります!

また、実際にはAmazonではなくSmart Connectionという店舗が販売しているため、それが気になる方も、2つ目のAmazonが販売しているパックの方が良いかもしれません。(ちなみに店舗の評判は、かなり良さそうです)

2つ目

2つ目がこちら!こちらはAmazonが直接販売しているものになります。

こちらは648円!(送料350円) 1つ目と比較すると多少割高ですが、Amazonという安心感はあります。

また現在、IIJmio SIMカード まとめ買いキャンペーンをやっており、対象商品を3,500円以上購入すると、こちらのパックが無料になるそうです!

このキャンペーンは、2016年5月9日(月)までの予定のようです。

IIJmioの縛り期間

音声通話機能付きSIMのみ縛りが発生します。

  • データ通信専用:縛りなし
  • SMS機能付き:縛りなし
  • 音声通話機能付き:12ヶ月

違約金は、残りの縛り期間、◯ヶ月×1,000円。

例えば4月に契約し、翌月5月に解約すると、残り11ヶ月なので、11,000円かかります。

ただし12ヶ月経ってしまえば、以降は縛りが更新されることはなく、いつでも無料で解約できます。

なんて良心的!(いや冷静に考えると、それが普通な気も…^^;)

ちなみに、今年の夏にLINEが格安SIMを出すらしいです。なんとLINEやFacebook等の通信料は無料!!めちゃくちゃ期待してます。

ただ、評判が見えてくるのを待たずに乗り換えるのは怖いので、今のうちにIIJmioに移っておいて、IIJmioの12ヶ月の縛りが切れる頃に、評判を見ながら乗り換えるか判断するのが安心かなと思っています。

まとめ

  • MNP転出時の料金

    • MNP転出手数料:ドコモ、AU 2,000円、ソフトバンク 3,000円
    • 解約違約金:契約と解約日により変動
    • 解約日までの日割り料金:定額系プランは全額支払うことになるため、ショップに事前相談が吉
  • IIJmio転入時の料金

    • 事務手数料:3,240円(税込)、ただし上述のウェルカムパックを買うことでかなり安く前払いできる
  • IIJmioの縛り期間

    • 音声通話機能付きのSIMのみ12ヶ月の縛り
    • 縛り期間中の違約金は、残りの縛り期間、◯ヶ月×1,000円
    • 12ヶ月後に縛りの更新はなく、いつでも解約可

最後に

IIJmioはインターネット事業の老舗。SIMの契約数もシェア2位なのに、速度も抜群に安定しています。(他社は契約数が増えると急に遅くなったりしている)

もし、MVNO事業者でお悩みでしたら、ぜひ信頼性の高いIIJmioもご一考されてはいかがでしょうか!?

最後まで見ていただき、ありがとうございました!

IIJ IIJmio SIM 音声通話 パック みおふぉん IM-B043

IIJ IIJmio SIM 音声通話 パック みおふぉん IM-B043

【TypeScript】target:'es6'でビルド後、実行すると"TypeError: ??? is not a function"が出る

f:id:m_doi2:20160309162719p:plain

TypeScriptでasync/awaitが使いたかったため、こちらの記事を参考にさせていただき、ビルド環境を整えました。

TypeScript 1.7 を Babel と組み合わせて async/await を使う | スマホ神 – JavaScript 受託開発 –

環境

  • Node v5.6.0
  • typescript v1.8.7

ビルド設定

参考にさせていただいた記事そのまま。

// gulpfile.js

const gulp = require('gulp');
const babel = require('gulp-babel');
const sourcemaps = require('gulp-sourcemaps');
const typescript = require('gulp-typescript');

gulp.task('default', () => gulp
    .src(['src/**/*.ts'])
    .pipe(sourcemaps.init())
    .pipe(typescript({ target: 'es6' }))
    .pipe(babel({ presets: ['stage-3', 'es2015'] }))
    .pipe(sourcemaps.write({ sourceRoot: 'src' }))
    .pipe(gulp.dest('lib')));

ビルドしたソース

async/awaitでexpressサーバーを起動。

// app.ts

/// <reference path="./typings/express/express.d.ts" />
'use strict';

import 'babel-polyfill';
import * as express from 'express';

(async () => {
  try {
    const app = express();
    const server = await Promise.resolve(app.listen(8080));
    console.log(`Started on port ${server.address().port}`);
  } catch(err) {
    console.error(err.stack);
    throw err;
  }
})();

エラー内容

ビルドは問題なく通るが、node app.jsで実行後、TypeError: express is not a functionが発生。

express以外でも、インポートしたものを関数として実行するタイプだと同様のエラーが発生。

原因

module: 'commonjs'の指定漏れ。

// gulpfile.js

const gulp = require('gulp');
const babel = require('gulp-babel');
const sourcemaps = require('gulp-sourcemaps');
const typescript = require('gulp-typescript');

gulp.task('default', () => gulp
    .src(['src/**/*.ts'])
    .pipe(sourcemaps.init())
    //.pipe(typescript({ target: 'es6' }))
    .pipe(typescript({ target: 'es6', module: 'commonjs' })) // ←module追加
    .pipe(babel({ presets: ['stage-3', 'es2015'] }))
    .pipe(sourcemaps.write({ sourceRoot: 'src' }))
    .pipe(gulp.dest('lib')));

本当に初歩的過ぎて、調べても全然出てこなかったので、記載しておきます^^;

【Node.js+Express4.x】REST APIで、エラーの内容毎に異なるステータスコードとエラーコードを出力する

f:id:m_doi2:20160303170941p:plain

Node.jsでREST APIを作っていて、エラー時のレスポンスをどうしようかなぁ、とごにょごにょ考えていました。

できれば、

  • エラー内容毎に、レスポンスのステータスコードを切り分けたい
  • クライアント側でエラーの種類を判別するために、独自のエラーコードも出力したい
  • エラー内容がなんとなくわかるメッセージは出力したい
  • エラー内容毎に、ログに出力するか否かも切り分けたい(システム的なエラーでない限りログに出てほしくないので)

の4点はクリアしたいなと。

これって一般的な方法はあるんでしょうか?

調べてわからなかったので、自前で実装してみました。

ソースコードはこちらにアップしてます。
https://github.com/m-doi2/express-error-code

簡単に説明

環境

  • node v5.6.0
  • express v4.13.4

ディレクトリ構成

app.js
errors
  ├─── abstracts
  │      └── abstract-errors.js // 自作エラーの抽象クラス
  │
  ├─── originals
  │      └── ・・・ // 自作エラークラス
  │
  └─── expand-error.js
       // Errorクラスを拡張し、ステータスコード、エラーコード、
       // コンソール出力の有無のプロパティを追加する関数 

自作エラー作成のための抽象クラス

// ./errors/abstracts/abstract-errors.js
'use strict';

/**
 * オリジナルのErrorオブジェクトの抽象化クラス
 * @constructor
 * @param {string} [message] エラーメッセージ
 * @public
 */
class AbstractError extends Error {
  constructor(message) {
    super(message);
    // 自身のクラス名をエラー名に指定
    this.name = this.constructor.name;
  }
}

module.exports = AbstractError;

Errorオブジェクトは、nameプロパティにエラー名を持っています。
例えばJavaScript標準のRangeErrorだと、RangeErrorという文字列が入っています。
このnameプロパティに、this.constructor.nameで自身のクラス名を入れることによって、子クラスが毎回エラー名を定義しなくて良くなります。

自作エラークラスのサンプル

// ./errors/originals/original1-error.js
'use strict';

const AbstractError = require('../abstracts/abstract-error');

class Original1Error extends AbstractError {
  constructor() {
    super('オリジナル1のエラーです');
  }
}

module.exports = Original1Error;

エラーメッセージが毎回変わるものではない場合、メッセージの引数はなしにして、superに固定で入れたほうが良いと思います。

ついでに、404のエラーも作成

// ./errors/originals/route-not-found-error.js
'use strict';

const AbstractError = require('../abstracts/abstract-error');

class RouteNotFoundError extends AbstractError {
  constructor() {
    super('ページが見つかりませんでした');
  }
}

module.exports = RouteNotFoundError;

Error拡張用の関数を用意

// ./errors/expand-error.js
'use strict';

const AbstractError = require('./abstracts/abstract-error');

// [statusCode, errorCode, outputLog]
const definitions = {
  // 40X
  RouteNotFoundError: [404, '4001', false],

  // 標準エラー
  Error:              [500, '5000', false],
  SyntaxError:        [500, '5001', true],
  RangeError:         [500, '5002', true],

  // 自作のエラー
  Original1Error:     [500, '5601', false],
  Original2Error:     [500, '5602', true],

  // 不明
  Unknown:            [500, '5000', true],
};

/**
 * Errorオブジェクトを拡張し、以下のプロパティを追加する
 *  - {number} [statusCode] HTTPのステータスコード
 *  - {string} [errorCode] エラー識別用のエラーコード
 *  - {boolean} [outputLog] エラー内容をログに出力するか
 *
 * @param {Object} [err] 拡張するエラーオブジェクト
 * @return {Object} 拡張後のエラーオブジェクト
 * @public
 */
function expandError(err) {
  let def = definitions[err.name] || definitions['Unknown'];
  err.statusCode = def[0];
  err.errorCode  = def[1];
  err.outputLog  = def[2];
  return err;
}

module.exports = expandError;

definitionsにエラー毎の値を設定してきます。
配列の1つ目がステータスコード、2つ目がエラーコード、3つ目がログ出力の有無となっています。
後は、expandError関数にErrorオブジェクトを突っ込むだけです。

使い方

// app.js
'use strict';

const express = require('express');
const app = express();
const bodyParser = require('body-parser');

const expandError = require('./errors/expand-error');
const RouteNotFoundError = require('./errors/originals/route-not-found-error');
const Original1Error = require('./errors/originals/original1-error');
const Original2Error = require('./errors/originals/original2-error');

// POSTをjsonで受ける
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());


// エラー発生用Routes(throwされてもnextに送っても動作可)
app.get('/error_any', function(req, res, next){
  // 任意のエラー
  throw new Error('メッセージ');
});
app.get('/error_basic', function(req, res, next){
  // 標準のエラー
  throw new RangeError('メッセージ');
});
app.get('/error_original1', function(req, res, next){
  // 自作のエラー
  next(new Original1Error());
});
app.get('/error_original2', function(req, res, next){
  // 自作のエラー
  next(new Original2Error());
});


// 404
app.use(function(req, res, next) {
  // Routeに一致しない場合は、404用のエラー出力
  next(new RouteNotFoundError());
});
// エラー
app.use(function(err, req, res, next) {
  // 既存エラーにコードを付加するためにエラー拡張関数を呼び出す
  expandError(err);

  // コンソールにエラー内容出力
  if (err.outputLog) {
    console.error(err);
    console.error(err.stack);
  }

  // RESTでエラー出力
  res.status(err.statusCode).json({
    statusCode: err.statusCode,
    errorCode: err.errorCode,
    message: err.message,
  });
});


// HTTPサーバ起動
let server = app.listen(8080, function() {
  console.log(`Started on port ${server.address().port}`);
});

404では、自作したRouteNotFoundErrorをnextに送るだけにし、実際には最後のエラー処理ミドルウェアで処理しています。

エラー処理ミドルウェアでは、expandError関数でエラーオブジェクトを拡張後、ログ出力の必要性があれば出力、最後にRESTでレスポンスを返しています。

終わりに

正しい実装方法があれば、ぜひご教示ください!よろしくお願いします。