今更ながら JSON のおさらい

一昔前(もっと前かもしれませんが..)には、データ交換のフォーマットといえば XML 、という時期がありました。

その流れで SOAPWSDLBPEL 、ESB などが生まれましたが、複雑すぎたのか、堅すぎたのか、最近ではあまり見ることがありません。(大規模な基幹システムなどでは採用されているのかもしれませんが)

最近の Web ベースの API でよく見るフォーマットは JSON です。

JSON は、JavaScript Object Notation の略であり、もともとは JavaScript のオブジェクトをあらわすものであったわけですが、その仕様は JavaScript の言語仕様に強く結びついているわけではないため、様々なデータを表現するのにばっちりはまりました。
API のコールを JavaScript で行う場合、レスポンスが JSON の場合はレスポンスの取り扱いが非常に容易であることもその一因かと思います。

そこで今更ながら、JSON の基本をおさらいしたいと思います。
厳密な仕様は http://json.org/ へ。

以下の6種類。

  • 文字列
  • 数値
  • 真偽値
  • null
  • 配列
  • オブジェクト


書き方

文字列 ダブルクォーテーションでくくる。
文字列にダブルクォーテーションを含める場合は、バックスラッシュでエスケープする。
"hoge"
"f\"oo"
など。
数値 数字をそのまま書く。
123
-234
12.6
など。
真偽値 true か false と書く。
null null と書く。
配列 [ と ] でくくり、カンマで各要素を区切る。すべての種類の値を要素にできる。
[1, 2, "hoge", false, null, [3, 2]]
など。
オブジェクト オブジェクトはキーと値のペア。キーは文字列でなければならない。値にはすべての種類の値を指定できる。
キーと値はコロンで区切り、キーと値のセットはカンマで区切る。
{ "price":1200, "title":"ほげほげ" }
など。


値の種類を組み合わせた例

{
  "hoge": [1, 2, {"foo":"aaa", "bar":"bbb"}],
  "piyo": { "key1":100, "key2":200 },
  "piyopiyo": 60.3
}




WebSocket を試す

Web といえば HTTP なわけですが、HTTP はあくまでプル型の機能です。つまり、自分から取りに行ってはじめて何らかの情報を得ることができます。

近年はいろんな API(Webサービス) が登場していて、それを利用することもあるかと思いますが、よくある REST API なども自分からコールしてその結果を得るものです。

RSSフィードにしても、Eメール(POP3)にしても、自分から取りに行く必要があります。
とはいえRSSフィードはそんなに頻繁に取得する必要はないですし、POP3 などは、短くても分単位でポーリングしておけば十分でしょう。

ただ、もっと速く、もっと短い間隔で、リアルタイムに情報を取得したいケースもあります。 しかしそんなときに1秒おきにリクエストを送信するなどして対応したら、攻撃とみなされてしまうかもしれません。

こういうケースでは、クライアントがデータを取りに行くのではなく、サーバ側起点で情報を送ってくれた方が効率的です。
プル型ではなくプッシュ型の方式ということです。

こういった要件を満たすために、ロングポーリングなどの方法が考え出されました。
WebSocket も、プッシュ型の通信を実現するものです。

最近のブラウザではだいたい WebSocket が使用できますので、この機能を試してみたいと思います。

データの取得先は、リアルタイムといえば取引ということで、仮想通貨取引所 Bitmex の WebSocketAPI を使ってみます。

<!DOCTYPE html>
<meta charset="utf-8" />
<script>
const ws = new WebSocket('wss://www.bitmex.com/realtime?subscribe=quote:XBTUSD');

ws.onopen = event => {
   console.log(event);
};
ws.onmessage = event => {
   console.log(event);
};
ws.onclose = event => {
   console.log(event);
};
</script>
<button onclick='ws.close();'>Close</button>

WebSocket のインタフェースはシンプルです。これだけで、コンソールログに情報がずらずらと流れます。




コマンドラインで Route53

EC2 のサーバで Web サイトを運用する場合、DNS には Route53 を利用される方もいらっしゃるかと思います。

Route53 は「可用性が高くスケーラブルなクラウドドメインネームシステム (DNS) ウェブサービス」(本家サイトより) です。


いくつかのドメインの Web サイトを EC2 インスタンスで運用していたのですが、そのインスタンスには Elastic IP アドレスを設定していませんでした。
つまり、停止 → 起動 を行うと、IP アドレスが変わってしまう状態でした。

あるとき、このインスタンスの能力を増強するためにインスタンスタイプの変更を行うことになったのですが、インスタンスタイプの変更を行うためには一度インスタンスを停止する必要があります。

インスタンスタイプの変更自体は問題なく行えたのですが、IP アドレスが変わってしまいました。

そのインスタンスにひもづいているドメインDNS レコードは Route53 で管理しており、インスタンスの IP アドレスが変わったので、各ドメインの A レコードを Route53 で変更しなければなりません。

1つ1つ修正する方法もありますが、数が多かったため、コマンドで変更することにしました。以下がそのコマンドです。(1ドメイン分)


aws route53 change-resource-record-sets --hosted-zone-id <ゾーンID> --change-batch '{"Changes":[{"Action":"UPSERT","ResourceRecordSet":{"Name":"<ドメイン名>","Type":"A","TTL": 300,"ResourceRecords":[{"Value":"<IPアドレス>"}]}}]}'


便利ですね。普通のドメイン管理サービスではなかなかこうはいきません。

ドメインのゾーンIDもコマンドで取得することができます。

aws route53 list-hosted-zones | jq -r '.HostedZones[]|[.Name,.Id]|@csv'



aws コマンドを使用するためにはそれなりに(もしかしたら面倒な)準備が必要ですが、それを補ってあまりある便利さです。




C# でチャットワークの API を使ってメッセージを投稿する

タイトルの通りです。

チャットワークには Web ベースの API が用意されており、それを使用することでプログラムからメッセージを投稿することができます。

今のところ認証は単一の API トークンによるもののみで、とてもシンプルです。 APIトークンはこちら https://www.chatwork.com/service/packages/chatwork/subpackages/api/token.php から確認できます。

投稿先(グループ)は、ルームID で指定します。URL で #!ridXXXXXXXXX となっている XXX... の数字部分がルームIDになります。

各エンドポイントについては公式ドキュメントに記載があります。メッセージを投稿する場合は /rooms/<ルームID>/messages です。
リクエストを作るところはこんな感じになります。

var req = (HttpWebRequest)WebRequest.Create("https://api.chatwork.com/v2/rooms/<ルームID>/messages");

req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.Headers.Set("X-ChatWorkToken", <APIトークン>);


ここで、コンテントタイプを application/x-www-form-urlencoded にするのを忘れないようにします。
チャットワークの API は、レスポンスは JSON 形式ですが、POSTする内容はURLエンコード形式になります。

POSTする部分はこんな感じになります。

var message = "こんにちは\n世界";
var content = "body=" + Uri.EscapeDataString(message);
var bytes = Encoding.ASCII.GetBytes(content);
req.ContentLength = bytes.Length;

new MemoryStream(bytes).CopyTo(req.GetRequestStream());

using (var resp = req.GetResponse() as HttpWebResponse)
using (var reader = new StreamReader(resp.GetResponseStream()))
{
    // レスポンスでは投稿したメッセージのIDが取れる
    reader.ReadToEnd();
}


メッセージに改行を入れれば、チャットワーク上でもちゃんと改行されます。
メッセージは body というパラメタで設定することになっているので、それを POST します。




C# で Worpdress の API を使って記事を投稿する

WordpressAPI が備わっていることはこれまでも記事で紹介していますが、今回は具体的に使う場合のコードのメモになります。

API では JSON でデータをやり取りしますので、DynamicJson を使用することにします。NuGet でインストールするかソースコードを取り込んで、 DynamicJson を使用できるようにします。

コードです。まず、送信するデータのもととなる匿名オブジェクトを作成します。

var postContent = new
{
    title = <記事タイトル>,
    content = <記事本文>,
    status = "publish", // 公開は"publish" 下書きは"draft"
    categories = "1,2,3", // カテゴリのIDをカンマ区切り
    tags = "4,5" // タグのIDをカンマ区切り
}

content には、普通に HTML タグを含む投稿の本文を文字列で指定します。

categories や tags などは不要であれば指定しなくてかまいません。
status も指定しなければデフォルトで公開状態になります。これらの他にもパラメタを指定でき、それは こちら で確認できます。

この匿名オブジェクトを JSON 形式の文字列に変換するのに、DynamicJson を使用します。

var serialized = DynamicJson.Serialize(postContent);

あとは送信するだけです。

var postData = Encoding.UTF8.GetBytes(serialized);

var req = HttpWebRequest.Create("<サイトのURL>/wp-json/wp/v2/posts") as HttpWebRequest;
req.Method = "POST";
req.ContentLength = postData.Length;
req.ContentType = "application/json"; // 送信するコンテントタイプを application/json にするのを忘れないこと

using (var reqs = req.GetRequestStream())
{
    reqs.Write(postData, 0, postData.Length);
}

using (var resp = req.GetResponse() as HttpWebResponse)
using (var reader = new StreamReader(resp.GetResponseStream(), Encoding.UTF8))
{
    var s = reader.ReadToEnd();
}

最後の変数 s には、API のレスポンスが JSON 形式の文字列で格納されています。

これをまた DynamicJson で扱うなら、

var post = DynamicJson.Parse(s);

こうすればOKです。こうすると、例えば投稿IDは post.id で取得できます。何が取得できるかはこちらで確認できます。




Redirect ディレクティブによるリダイレクト

ある URL にアクセスされた場合、それを別の URL に飛ばしたい場合があります。

それは内部的なものと外部的なものに分けられます。

内部的なものは、ある URL にアクセスされたときに、その URL を処理する機能を別の機能にまかせたいケースです。
Wordpress が自動的に .htaccess に追加するリライトの設定などがそれにあたります。
Wordpress の場合は、/foo/bar などのアクセスがあった場合、基本的には index.php に処理が転送されます。
ブラウザのアドレスバーに変更がないのが特徴です。

外部的なものは、単に別の URL に転送したいケースです。これには HTTP のリダイレクトが使用されます。リダイレクトでは、ブラウザのアドレスバーに表示される URL は転送先のものになります。
リダイレクトでは、同じサイト内で別の URL に飛ばしたい場合と、他のサイトへ飛ばしたい場合があります。


行いたいのは大抵は外部的な URL の転送だと思いますが、リダイレクトする方法を検索すると、リライトによる方法を紹介しているページが多く目に付きます。
しかし、複雑なことをしないのであれば、Apache であればリライトではなく Redirect ディレクティブによるリダイレクトで十分なケースもあるでしょう。

URL階層を引き継いで、別のサイトに飛ばしたい


例えば、http://foo.test/aaa/bbb.htmlhttp://bar.test/aaa/bbb.html に飛ばしたいというケースでは、以下の設定が使えます。

Redirect / http://bar.test/

301リダイレクトにする場合は、以下のようにします。

Redirect permanent / http://bar.test/
#または
RedirectPermanent / http://bar.test/

/aaa の下だけを飛ばしたい場合は、以下のようにすればOKです。

Redirect /aaa http://bar.test/aaa/


URL階層を引き継がないで、別のサイトに飛ばしたい


http://foo.test/aaa/bbb.htmlhttp://bar.test/ に飛ばしたいなど、/aaa/bbb.html であろうが ccc.html であろうが、とにかく http://bar.test/ という1つの URL に飛ばしたいというケースです。

この場合は、RedirectMatch を使用することで実現できます。

RedirectMatch .* http://bar.test/

RedirectMatch は本来、正規表現を使用して Redirect ディレクティブでは実現できない少し複雑な転送を行うものですが、RedirectMatch は Redirect ディレクティブのように URL 階層を引き継ぎませんので、この目的で使用することができます。



certbot-auto で ImportError: No module named cryptography.hazmat.bindings.openssl.binding エラー

AWS の EC2 で運用しているWebサイトがあり、そのサーバでは certbot-auto を使用してサイトのSSL化を行っていました。

先日、certbot-auto を使用してSSL証明書の作成を行おうとしたところ、以下のエラーが発生しました。

Upgrading certbot-auto 0.27.1 to 0.28.0...
Replacing certbot-auto...
Creating virtual environment...
Installing Python packages...
Installation succeeded.
Traceback (most recent call last):
  File "/opt/eff.org/certbot/venv/bin/letsencrypt", line 7, in <module>
    from certbot.main import main
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/certbot/main.py", line 10, in <module>
    import josepy as jose
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/josepy/__init__.py", line 44, in <module>
    from josepy.interfaces import JSONDeSerializable
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/josepy/interfaces.py", line 8, in <module>
    from josepy import errors, util
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/josepy/util.py", line 4, in <module>
    import OpenSSL
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/OpenSSL/__init__.py", line 8, in <module>
    from OpenSSL import rand, crypto, SSL
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/OpenSSL/rand.py", line 12, in <module>
    from OpenSSL._util import (
  File "/opt/eff.org/certbot/venv/local/lib/python2.7/dist-packages/OpenSSL/_util.py", line 6, in <module>
    from cryptography.hazmat.bindings.openssl.binding import Binding
ImportError: No module named cryptography.hazmat.bindings.openssl.binding

Pythoncertbot も詳しくないのですが、タイトルのエラーについてはネット上にいくつも情報が転がっていました。

ただ、どれも微妙に異なる内容で、検索結果の上から試していきましたが一発では解決しませんでした。

結論、この環境では以下で復旧させることができました。

unset PYTHON_INSTALL_LAYOUT
/opt/eff.org/certbot/venv/bin/pip install --upgrade certbot

しかしネット上の情報と上記のエラーとを総合すると、原因は certbot-auto がアップグレードをかけるときに上手くいっていないためだろうと考えられました。
であるならば、certbot-auto を実行する前に unset PYTHON_INSTALL_LAYOUT すればよいのではないかと考え、別のサーバで unset PYTHON_INSTALL_LAYOUT してから certbot-auto を実行してみたところ、無事にアップグレードされました。

一度 certbot-auto してこのエラーが出てしまった後では、/opt/eff.org/certbot/venv/bin/pip install --upgrade certbot で明示的にアップグレードしてあげる必要があります。