何を解決したいか?
Mac, Windows, Linux, iPhoneやAndroidのスマホ・タブレットとかのデバイス間でデータの転送したいことがあります。
SlackとかLineとかSkypeとかAirDropとかあっても
- 送りたい相手と共通して使っているサービスを探す必要とか、
- GUIのソフトウェアのインストールが必要とか、
- AirDropだとApple系OSである必要
があるなどの転送の障壁があって、GUIが使えないデバイスに送りたいときなどは困ってしまいます。
すでにたくさんのファイル共有系のサービスがありますが、コマンドを使ったCUIベースにあまり親切な設計なものはあまりないと思います。
そこで、上記の問題を解決するために、以下のようなファイル転送の仕組みを作りました。
- 他デバイス間でデータ転送ができ、
- 別途ソフトウェアのインストール不要で、
- パイプにとても親和性が高くエンジニアフレンドリー
実際の動作
データの送信者も受信者も転送するためにインストールするものはありません。
curlコマンドやブラウザなどHTTP通信ができるものがあれば転送できます。
以下のデモでは、seq 100000
を上のマシンから下のマシンに送っています。
これはseq 100000
以外にもcat
でもls
でもパイプ渡せるあらゆるものが転送できます。例えば、ファイルが送りたいときはcat ファイル名
とします。
以下のように、100MB.dat
をデバイスをまたいで送れます。
エンドツーエンドの暗号化なども可能です。
パイプが使えるということは、圧縮や暗号化など、また2つ組み合わせて圧縮+暗号化すること可能です。
圧縮送信: cat myfile | gzip | curl -T ...
解凍受信: curl ... | zcat > myfile
や
暗号化送信(共通鍵): cat myfile | openssl aes-256-cbc | curl -T ...
復号化受信(共通鍵): curl ... | openssl aes-256-cbc -d > myfile
暗号化にgpg
を使ってもいいですし、将来の最新の暗号方式や圧縮方式が出たらそれを使うことも可能です。
一時的なファイルも経由せず、時間的にも空間的にも効率が良く、パイプの素晴らしい可能性が発揮できます。
他にもパイプを使った便利なデータ転送を後述します。
最小の転送機能を搭載したWeb Clientでもファイルを送ったり受け取ったりすることが可能です。
IE10やIE11でも動作可能でした。
以下のデモのようにターミナルからブラウザに画像ファイルを送ることも簡単です。
以下のようにブラウザからもファイルを送ることができ、curl
で受け取れます。
追記: もう少しリッチなWeb UIができました:
「Web上でファイル転送をスマホでもPCでもcurlでも手軽にしたい! - Qiita」
まとめると、
- 従来のHTTP通信で転送可能
- 成熟した枯れた技術に支えられている
- データはストリーミングされる
- Unix/Linuxなどのパイプで効率よく転送できる
- サーバーはデータを経由させるだけで保存は一切しない
- Stream APIの力でうまいこと背圧制御とかもされる
です。
追記(2019/02/13)
@Cryolite さんによって、Piping Serverを使って任意のネットワークコネクションを確率する手法が発案されました。具体例では、外向きTCP80ポートの許可のみでssh接続が可能なことが示されています。以下の記事にて原理について詳しく紹介されています。
Piping Server を介した双方向パイプによる,任意のネットワークコネクションの確立 - Qiita
GitHubリポジトリ
プロジェクトはPiping Serverと呼んでます。デバイス間のデータをネットワーク越しでパイプさせたい思いをこめました。
サーバーサイドTypeScriptです。
GitHub: https://github.com/nwtgck/piping-server
サーバー
どこかのサーバーが独占するわけではなく、サーバーは自由にたくさん立てるポリシーです。好きに自分用のサーバーを立ていただければと思います。デモ用にいくつかサーバーを立てました。 1
- https://ppng.io/
- https://piping.glitch.me/
- https://ppng.herokuapp.com/
- https://piping-47q675ro2guv.runkit.sh/
オススメはhttps://ppng.ioです。この中で一番短いのでコマンドのタイプ数を削減できます。
その他個人的な運用では、LAN内にPiping Serverを立ててLANではより高速かつ閉じた転送するのに使ってます。主にLAN内ではSSHなどが使えない場合、特にLinux<=>Windowsなどで使ってます。
使い方が忘れたときはGET /helpで簡単にヘルプが確認できます。
$ curl https://ppng.io/help
ヘルプが出力される
サーバーの立てやすさもこだわっていて、
- ポータブルなバイナリで起動させたり、
-
docker run
で起動できたり、
自分用のサーバーを手軽に立てれるようにしてます。サーバーの立て方は、下の方に書きたいと思います。
データ転送の仕組み
HTTPを使います。また、NAT超え問題を解決するために、サーバーが送信者と受信者の経由役になります。おそらくピアツーピア(P2P)のTURNサーバに似たものだと思います。
多くのデバイス・OSでの利用できるようにするためとインストール不要を実現するために、
広く広まっているプロトコルでの通信が不可欠です。そこで通信はHTTP/HTTPSを使います。
CUIからは、curl
やwget
で簡単にデータを送るのも、受け取るのも可能です。curl
やwget
は最初からインストールされている事が多いです。
macOS, Ubuntu, Amazon Linuxなどよく使われているOSには最初から入っています。
Windows も Windows 10 version 1803 から標準でcurl
コマンドが入るようになりました。
しかもcurl
は広く使わていて枯れた技術であり、とても信頼できます。
HTTPを使っているため、GUIでもブラウザからデータを送ることも受け取ることもです。
ブラウザならMacでもWindowsでもiPhoneやAndroidなどのスマホ・タブレットでも使えます。
送受信は手軽行えるように極限までシンプルにしました。
- 送り方は、任意のパスを指定して、POSTかPUTのボディに送りたいデータを付けて送るだけです。
- 受け取り方は、そのパスを指定してGETすればダウンロードできます。
セキュリティのために、データはサーバ上に保存せず、ストリーミングされて転送されます。
このストリーミングのおかげでUnix/Linuxのパイプと親和性が高く、効率よく、大きなデータの転送が可能になりました。
セキュリティ
セキュリティに関してはデータ転送においてとても重要なところです。
まず、HTTPSを使っていれば、クライアント<=>サーバー間は暗号化されます
上記に書いたopenssl ...
やgpg
コマンドなどを使えば、エンドツーエンドの暗号化も可能になり更に安全になります。
上で紹介したデモ動画だと、「送信者がPOST/PUTしたあと、受信者がGET」という順序ですが、「受信者がGET、送信者がPOST/PUT」の順序でも送信できます。後者のほうがよりセキュリティ的には良いと思います。
理由としては前者だとの方法だと、送信者が先に送る準備をしてしまうため、真の受信者がGETする前に、他の受信者がそのパスを指定しまうと、データを得れるからです。もちろんパスを十分に推測不能なものにすればいいのですが、その状況を防げるGET firstがより安全です。
パスは早いものが勝ちになるので、以下のように既に受信者がいる場合は拒否されます。たまたま、ぶつかる可能性があるときも先にGETして予約していたほうが、送信者との安全なコネクションがはれると思います。
- GET /mydata => 送信者を待つ
- GET /mydata => 拒否されて接続が切断 (すでに待ち状態の人がいるから)
- POST /mydata => 1の人に転送される
複数人に送信
/mypath?n=3
のように受信者の人数を指定できる機能があります。n
を指定しないときはn=1
になるようになっていて、今まではn=1
で転送されていました。
サーバーに保存されないため、一度ストリームが流れてしまうと同じデータを受け取りたい人に送るときの効率が悪いです。それを解決するための機能になってます。
複数人に送るときのセキュリティ
n=3
のときは、4人目が受信しようとした時点で、4人目の接続が拒否され切断されます。つまり、n
を指定しないn=1
のときは2人目受信しようとした時点で2人目の接続が拒否され切断されます。n
を一般化しているだけなので、上記の通りGET firstなコネクションがより安全な転送方法になると思います。
複数人に送る技術的な話
データの元のストリームがあってそれを複数に分配するような仕組みが必要です。イメージ的には以下の図のようになります。
----------> HTTP GET 受信者1
|
送信者 HTTP POST ---サーバー-----> HTTP GET 受信者2
|
|
...
----------> HTTP GET 受信者N
サイバーサイドTypeScriptなので、Node.jsを使っています。
stream.PassThroughを使って実現が可能です。
StackOverflowに短い例があったので、引用させていただきます。
下の例だとecho hi user
の出力の文字数をカウントしつつ、出力の文字列自体も表示しています。
const spawn = require('child_process').spawn;
const PassThrough = require('stream').PassThrough;
const a = spawn('echo', ['hi user']);
const b = new PassThrough();
const c = new PassThrough();
a.stdout.pipe(b);
a.stdout.pipe(c);
let count = 0;
b.on('data', function (chunk) {
count += chunk.length;
});
b.on('end', function () {
console.log(count);
c.pipe(process.stdout);
});
8
hi user
new PassThough()
を作って、そのPassThoughを最初に.pipe()させて、もとのストリームではなくPassThoughのメソッドの.on()や.pipe()を使って処理をしているのが分かると思います。
HTTPサーバーを作るときのhttp.IncomingMessage
やhttp.ServerResponse
でも同じように使えます。
実際にPiping ServerでのPassThoughを使っている箇所は以下になります。
https://github.com/nwtgck/piping-server/blob/eb40fe251b2a552348a4f8f8e635529460f2b4fa/src/piping.ts#L299-L362
Piping Serverの立て方
すぐに気軽に立てれるように、いくつかサーバ立てる方法を用意しています。
Herokuに立てる
Herokuに立てる方法についてです。
以下のPiping Serverのリポジトリにある紫色の[Deploy to Heroku]を押すと、手軽にデプロイできます。
GitHub: https://github.com/nwtgck/piping-server
ボータブルなバイナリ実行形式
zeit/pkgを使って、npmで管理されているプロジェクトをポータブルなバイナリしています。
node
本体とソースコードを含めることでnode
をインストール不要で動かせるようになっています。
以下のコマンドで、ダウンロードして、実行権限を与えて http://localhost:8888にPiping Serverが立ち上がります。
wget https://github.com/nwtgck/piping-server-pkg/releases/download/v0.8.4/piping-server-macos
chmod +x piping-server-macos
./piping-server-macos --http-port=8888
環境を汚さないのと導入が手軽なのがメリットです。
...-alpine
、...-macos
、...-linux
と...-win.exe
がGitHub releaseにあります。gitのタグがつくとCI上で自動でGitHub releaseビルドしたバイナリをデプロイするように自動化しました。現在はPiping Serverと別リポジトリですが、将来はマージされる可能性があります。
Docker
以下のdocker run
で、バックグラウンドで動作しマシンの再起動後も自動起動でhttp://localhost:8888にPiping Serverが立ち上がります。
docker run -d --restart=always -p 8888:80 nwtgck/piping-server --http-port=80
Piping Serverはデータを保存しないので、-v
での永続化もデータベースに繋ぐ必要もありません。
Docker imageもgitのタグがつくと、Docker automated buildで自動でイメージがビルドされるようになっています。
npm
Piping Serverはnpmのパッケージなので、以下のようにインストールすることができます。
npm install -g piping-server
以下のように起動させると、http://localhost:8888にPiping Serverが立ち上がります。
piping-server --http-port=8888
これもgitのタグがつくと自動で、npm publishされるようにCIで自動化して人為ミスなどを減らしています。
npx
npx
を使えば以下のように起動できます。
npx piping-server --http-port=8888
Docker(bytenode
)
これは実験的なプロジェクトです。
bytenodeというJavaScriptをGoogle V8のバイトコードに事前にコンパイルしてそれを実行するプロジェクトがあり、それを利用したDocker imageです。メリットとしてはDocker imageが軽量です。
webpackなどの代替のparcelを使って、bundleして、それをバイトコードしてそれだけでalpineベースのDocker imageに乗せているので、より軽量なイメージになっています。
これも実験的なので別リポジトリです。
https://github.com/nwtgck/bytenode-piping-server
イメージ名以外起動方法が変わりませんが、イメージサイズがより軽量です。
docker run -d 8888:80 nwtgck/bytenode-piping-server --http-port=80
現在実験的なプロジェクトです。ある程度使ってみて安定してそうだったら、本家のイメージもこの方法で作るようになるかもしれません。
HTTPでのデータ転送の可能性
「HTTPってどこまで大きいファイル送れるの?」 「HTMLとかRESTのAPIサーバーとか小さいデータを返すイメージしかない」という疑問に関してです。
Piping Serverで無限にデータを送り続ける実験を47日前に開始しました。現在も進行中で、その結果では現在868テラバイトのデータが転送できています。
無限に送るのは単純で/dev/urandom
をcatしているだけです。
cat /dev/urandom | curl -T - localhost:8888/rand
curl localhost:8888/rand > /dev/null
この結果から少なくともlocalhostなら868テラバイトは転送できることが体感できました。
curl -T -
とは
この記事でよく登場しているcurl -T - ...
はPUTメソッドで、標準入力(=パイプされたもの)をHTTPのボディにつけて送ってくれるオプションです。-T
==--upload-file
です。
-
を使わずにcurl -T ファイルパス ...
の場合は指定されたファイルを送ることができます。
PUTではなくPOSTを使いたいときは、以下でもできます。ただPiping ServerではタイプしやすいようにPUTに対応してます。
cat myfile | curl --data-binary @- https://ppng.io/mydata
パフォーマンス比較 - Go言語との比較
オリジナルの実装がTypeScript/Node.jsです。そこで、Go言語で最小限のPiping Serverを実装して転送速度の比較をしました。転送にかかる時間は大きく差があればあるほど、アップロード・ダウンロードともにユーザー体験が損なわれるので、調べました。比較対象としてGoを選んだのは、新しいサービスがGoで書かれていることが多くGoとの比較に興味があったのと、ネイティブな実行形式を作り出せるGoとスクリプト言語のJavaScriptでどれだけ差があるか知りたかったからです。
比較と言っても、そこまできっちりしたベンチーマークとかではなく、実際に送ってみて何MB/secか目視で確認する感じです。結果的にはそこまで顕著な差で出なさそうなことが分かりました。動画として結果を記録しました。
TypeScript/Node.js
YouTube: https://youtu.be/ufijpqHlJww
(https://github.com/nwtgck/piping-server)
Go言語
YouTube: https://youtu.be/NuKvDwB9JSU
(https://github.com/nwtgck/go-piping-server)
クライアントは無限にyes
でy
をlocalhostのPiping Serverに流し込みます。クライアントは送信者と受信者の組が3で、少しずつ増やしていきました。少しずつ増やしていくと、速度が41~42MB/s=> 38MB/s=>32~34MB/s のように変化する感じで、Node.jsもGoもだいたい同じくらいの速度でした。Node.jsはスクリプト言語ですが、データ転送部分で使ってるReadableStream#pipe()
の裏の実装がC++とかで書かれてて早いのかと勝手に推測してます。C++でのpipeの実装はこれかもしれません -> (node/src/stream_pipe.cc)
パイプを活かした使用例
よく使いそうな例をまとめます。
パイプの可能性が発揮できそうな面白いものも後ろの方にまとめました。
gzip圧縮して送る
gzip
, zcat
で圧縮送信、解凍受信可能です。
echo hello | gzip | curl -T - https://ppng.io/mymsg
curl https://ppng.io/mymsg | zcat
暗号化して送る
openssl aes-256-cbc
を使うとパスワードを使って共通鍵暗号方式でエンドツーエンドの暗号化が実現できます。
echo hello | openssl aes-256-cbc | curl -T - https://ppng.io/mymsg
curl https://ppng.io/mymsg | openssl aes-256-cbc -d
ディレクトリを送る
ポイントは、以下のどちらのコマンドも一時ファイルを作らずにストリームでzipやtar.gzを作って、ディレクトリを転送できるところです。
zip -q -r - ./mydir | curl -T - https://ppng.io/mydir.zip
tar zfcp - ./mydir | curl -T - https://ppng.io/mydir.tar.gz
プログレスバーを出しながら送る
pv
コマンドを使うと進捗のバーが出てくれます。
汎用的な方法は、以下のようにパイプとパイプの間に挟むことで進捗がわかります。
... | pv | curl -T - ...
ファイルを送りたいときは、以下のようにcat
の代わりにpv
を使うことでOO/100%の形でバーが見やすくなります。
WindowsでもCUIで転送したい!
PowerShellを使う方法の紹介です。
PowerShellは以下のアイコンのやつです。コマンドプロンプトとは別のアプリケーションで、少なくともWindows 10なら標準で入っているはずです。
iwr
コマンド(=Invoke-WebRequest
標準のエイリアスのはずです)を使うとある程度curl
の代わりになります。
iwr https://ppng.io/mydata -InFile ./mydata.dat -Method Post
iwr https://ppng.io/mydata -OutFile mydata.dat
cat ./hello.txt | iwr -InFile $_ -Method POST https://ppng.io/mymsg
(PowerShellが使えなくても、ブラウザでファイルの送信・受信が可能です)
PowerShellはWindowsに限ったものではなく、マルチプラットフォームを目指しているようです。
個人的に一番お手軽に試せると思うのがDockerを使った方法です。
以下のコマンドでシェルが立ち上がります。
docker run -it microsoft/powershell
vi系のエディタで送る
viやvimなどのエディタを使って一時ファイルを作らずにPiping Serverに送る方法です。
以下のように、:w !curl -T - ...
で書いている内容を送ることができます。
標準入力をリアルタイムに送る
以下のように、cat | curl -T - ....
を使うと入力の内容が改行ごとに即時送られます。
(追記: -T -
で標準入力を受け取るので、cat |
はなくてもOKですね。)
ハッシュ値を計算しながら送る
送りながらハッシュ値を計算することで、より自信をもってデータを転送できます。
これのポイントも同じく、一時ファイルを作らずにストリーミングしながらハッシュ値を計算しているところです。
>(shasum ...)
という記法はProcess Substitutionという機能で、bashやzshなどに搭載されています。
seq 100000 | tee >(shasum >&2) | curl -T - https://ppng.io/myseq
curl https://ppng.io/myseq | tee >(shasum >&2) > seq.txt
上記のコマンドを理解するためには、tee
とProcess Substitutionの両方の知識が必要です。標準入力をtee
は与えられたファイルに書き出しつつ、標準出力にも吐き出します。
>(shasum >&2)
を使うとshasum
の標準入力の部分がファイルになってくれてるイメージで、以下のようにecho
すると雰囲気がだいたいつかめると思います。
$ echo >(shasum >&2)
/dev/fd/63
>(shasum >&2)
でshasum
の標準入力へのファイルパスが得られるため、tee
でうまく動くという感じだと理解しています。
動画をストリーミングで見たい
以下のようにmp4の動画をPiping Serverに送って、ブラウザで開けば簡易的なストリーミングで動画が視聴できます。
cat ray.mp4 | pv | curl -T - -H 'Content-Type: video/mp4' https://ppng.io/myvideo
ポイントは、すべての動画がダウンロードされる前から、再生されているところです。簡易的なストリーミングできています。
これに対応したmp4を作成するには、プログレッシブダウンロードに対応させる必要があります。
以下のコマンドでinput.mp4
からプログレッシブダウンロード対応のoutput.mp4
を生成できます。
ffmpeg -i input.mp4 -codec copy -movflags faststart output.mp4
参考: プログレッシブダウンロード対応の MP4ファイル を作成する :Tips & FAQ - arbk-works
ターミナルの画面を共有する!!
script
コマンドとcurl
があれば、ターミナルの画面共有も可能です!
共有したい画面の人は以下のコマンドをたたきます。
script -f >(curl -T - https://ppng.io/myscript >&/dev/null)
(Macの場合はscript -f
の代わりにscript -F
)
curl https://ppng.io/myscript
/myscript
の箇所は適宜変えて使ってください。
仕組みとしては、script
コマンドというターミナル作業ログをとるコマンドの力です。
ログをファイルに取れて、Process Substitution>(curl -T -)
に向かってログするようにしています。ネットワーク越しにこれをパイプできるPiping Serverに渡るため、画面を見る人はただcurl ...
するだけで画面を見ることができます。
魅力は既存のインストールされているであろうscript
コマンドとcurl
だけでターミナル共有が完結するところです。また、色のついた文字やtop
やsl
など動きが激しい画面もうまく共有できています。/myscript?n=3
などとすれば、3人に画面共有することもできます。
【追記】 Piping Server使った他の色々
(追記:2019/03/23)
Piping Server - @nwtgck/Ryo Ota
上記ののリンク先で、
- HTTP/2対応
- GraalVM上での起動
- ロードバランサー対応
- 暗号通話
- ...
などについて書いてます。今後もPiping Serverの活用/応用など増えていくかもしれません。
【追記】 以前、似たもの作っていなかった?
(追記: 2019/02/07)
Piping Serverと同様にOver HTTP/HTTPSでファイル転送したい思いで以前に作っていました。
以下のQiita記事で紹介していたTrans Serverというのを作っています。
Qiita: 標準コマンドだけでファイルを送りあえるサーバーを作ってみました - Qiita
GitHub: https://github.com/nwtgck/trans-server-akka
Piping Serverと違って、ストリーミングせずに後でダウンロードしたいという要望を叶えるために作りました。
ほぼPiping Serverとインターフェースの部分は同じです。
違うところが、送信後にデータのIDが返されて、そのIDを使って好きなタイミングでダウンロードできるところです。
そのため、Piping Serverではできないデータ転送をしたいときに使っています。
ダウンロード回数制限や、一定時間での削除、IDを長くしてセキュリティの強化などの機能がついています。
技術はScala/Akka HTTP/Akka Streamなどを主に利用しています。
-
コマンドで完結するため、広告などでサーバー運営費をまかなえるようなサービスではないので、予告なしにサーバーと止めることがあると思います。サーバーは手軽にたてれるように心がけたので、止まってしまった時はご自身で立ていただければと思います。 ↩