HTCPCPについて

# はじめに
この記事は[ロボP Advent Calendar 2018](https://qiita.com/advent-calendar/2018/robop)の8日目の記事です。
# RFCとは
## ガチのRFC
[RFC-Wiki](https://ja.wikipedia.org/wiki/Request_for_Comments)を参照
> Request for Comments（リクエスト フォー コメンツ、略称：RFC）はIETF（Internet Engineering Task Force）による技術 仕様の保存、公開形式である。内容には特に制限はないが、プロトコルやファイルフォーマットが主に扱われる。RFCとは「コメント募集」を意味する英語の略語であり、もともとは技術仕様を公開し、それについての意見を広く募集してより良いものにしていく観点から始められたようである。全てのRFCはインターネット上で公開されており、誰でも閲覧することができる。


## Joke RFC
RFCのうち内容が冗談なものを指します。エイプリルフールに一種のネタとして発行されるものです(近年はネタ切れにより毎年発行されるわけではないらしい)
有名なものとして挙げられるのが、[鳥類キャリアによるIPデータグラムの伝送規格](http://www.kt.rim.or.jp/~ksk/joke-RFC/rfc1149j.txt)や[洗濯バサミDHCPによるIPアドレスの管理手法](http://www.kt.rim.or.jp/~ksk/joke-RFC/rfc2322j.txt)などがあります。
ネタ的な位置付けにありますが、これらの規格を実装した猛者がいるみたいです(鳥類キャリアはノルウェーの方や南アフリカの企業が実装、実験を行った)
本物(?)のRFCよりも人気のあるものです

# RFC2324(HTCPCP)
今回はこちらのジョークRFCについてご紹介させていただきます。
## 概要
HTCPCPとは Hyper Text Coffee Pot Control Protocolの略でHTTP上でコーヒーポッドを制御できるプロトコルです。ジョークRFCの一種ですが正式に規定されたプロトコルです。このプロトコル自体実行可能で、Emacsでは実装するためのモジュールがあるらしいです(~~Vim使ってるのでよくわかりません~~)。ただしHTTPをベースとしているので、コーヒーポッド側から通知を飛ばせないといった問題があるっぽい(~~元々ジョークで発案したからいいんです!!~~定義されたのがだいぶ昔で現在はIoTを駆使すれば実装できるっぽい)
## メソッド
HTTPベースなのでメソッドもある。具体的には以下のものがある。

- BREW(POST)
    - コーヒー沸かしを制御するコマンドを送るときに使われる。メッセージ本文のContent-Typeヘッダーは"application/coffee-pot-command"となる
    - コーヒーサーバはPOSTとBREWを等しく使う必要があるが、コーヒーサーバを作動させる目的でPOSTを使うことは推奨されない
- GET
    - GETメソッドは「要求URIによって特定される何らかの情報を、本文として送信せよ」という意味である。
- PROPFIND
    - コーヒーがデータの場合、コーヒー生成に関するメタデータはこのメソッドにより取得できる
- WHEN
    - ミルクが注がれる場合、入れてもらう側は適当な時間でミルクの提供を止めてもらう必要がある。そのためのメソッドである。コーヒーサーバはWHENの受信がないままミルクが溢れそうになったら、生成オブジェクトを破棄しても良い。

## URI
通称コーヒースキーム。こんなかんじ
`coffee://パス/ポッド識別子?(#)添加物リスト`
## 実装
簡易的なものですが作ってみました
ソケット通信プログラムは[こちら](https://qiita.com/__init__/items/5c89fa5b37b8c5ed32a4)を参考にさせて頂きました。

まずはサーバー

```Python:server.py
import socket

def detect_method(uri):
    data=""
    header_parts = {
            'HTCPCP' : '200 OK',
            'Cache-Control' : 'private',
            'Content-Type' : 'message/coffeepot',
    }

    if 'BREW' in uri:
        for k,v in header_parts.items():
            data+=str(k)+":"+str(v)+"\r\n"
        data+="\r\nstart\r\n"

    elif 'GET' in uri:
        data+="418 - I\'m a tea pod.\r\n"

    else:
        data+="Bad request!!\n"
    
    return data

# AF = IPv4 という意味
# TCP/IP の場合は、SOCK_STREAM を使う
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    # IPアドレスとポートを指定
    s.bind(('127.0.0.1', 50007))
    # 1 接続
    s.listen(1)
    # connection するまで待つ
    while True:
        # 誰かがアクセスしてきたら、コネクションとアドレスを入れる
        conn, addr = s.accept()
        with conn:
            while True:
                # データを受け取る
                data = conn.recv(1024)
                if not data:
                    break
                query = data.decode('utf-8','replace')
                if query.find('HTCPCP') != -1 and query.find('coffee') != -1:
                    res = detect_method(query)
                else:
                    res = "None"
                print(res)
                #print('data : {}, addr: {}'.format(data.decode('utf-8','replace'), addr))
                # クライアントにデータを返す(b -> byte でないといけない)
                conn.sendall(res.encode())
```
続いてクライアント側

```Python:client.py
import socket
import sys

args = sys.argv

if len(args) == 4:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        # サーバを指定
        s.connect(('127.0.0.1', 50007))
        # サーバにメッセージを送る
        sdata = args[1] + " " + args[2] + " " + args[3]
        s.sendall(sdata.encode())
        # ネットワークのバッファサイズは1024。サーバからの文字列を取得する
        data = s.recv(1024)
        #
        print(data.decode('utf-8','replace'))
else:
    print("use 4 param(filename HTCPCP scheme method)")
```
結果はこうなった

```
#client側

$ python client.py HTCPCP coffee://aaa/pod-0?milk BREW
HTCPCP:200 OK
Cache-Control:private
Content-Type:message/coffeepot

start

$ python client.py HTCPCP coffee://aaa/pod-0?milk GET
418 - I'm a tea pod.

$ python client.py HTCPCP coffee://aaa/pod-0?milk WTF
Bad request!!


```

```
#server側

$ python server.py
HTCPCP:200 OK
Cache-Control:private
Content-Type:message/coffeepot

start

418 - I'm a tea pod.

Bad request!!



```
ジョークRFCなので定義がガバい箇所があり、細かいところまでは実装できませんでした。
特にリクエストヘッダやレスポンスヘッダの定義が曖昧だったので、そこが悩みどころでした。
# ちなみに
HTCPCPプロトコルを実装してラズパイを用いてコーヒーメーカーを制御している方がいるらしいです
Youtubeとかで実際の様子が見れます。すごかったです、ほんとに。
頑張れば僕も実装できそうなのでやってみたいです(**時間とお金と余裕があれば・・・!**)
また、派生として[RFC7168：ハイパーテクストコーヒーポット制御プロトコル](https://tools.ietf.org/html/rfc7168)というものもあるので暇な人は実装してみてください

※現在(2018/12/08)、HTTP/1.1ではHTCPCPで用いられる418ステータスコードを利用しないよう改定されたみたいです。元々ジョークなので致し方ないと言えますが・・・　来年のエイプリルフールに期待ですね
# 感想
ジョークとは言え、プロトコルとして成り立っているのはすごいと思いました。
HTTP上で実装するプロトコルなので、リクエストヘッダ(レスポンスヘッダ)まわりの復習もできたのでいい経験になりました。
# 参考資料
- [Hyper Text Coffee Pot Control Protocol - Wikipedia](https://ja.wikipedia.org/wiki/Hyper_Text_Coffee_Pot_Control_Protocol)
- [RFC2324 (http://www.chibutsu.org/iorin/rfc/rfc2324.txt)](http://www.chibutsu.org/iorin/rfc/rfc2324.txt)
- [coffee scheme (https://wiki.suikawiki.org/n/coffee%3A)](https://wiki.suikawiki.org/n/coffee%3A)
- [Hyper Text Coffee Pot Control Protocol まとめ (Hyper Text Coffee Pot Control Protocol)](Hyper Text Coffee Pot Control Protocol)