Edited at

ネットワーク越しでパイプしたり、あらゆるデバイス間でデータ転送したい!


何を解決したいか?

Mac, Windows, Linux, iPhoneやAndroidのスマホ・タブレットとかのデバイス間でデータの転送したいことがあります。

SlackとかLineとかSkypeとかAirDropとかあっても 


  • 送りたい相手と共通して使っているサービスを探す必要とか、

  • GUIのソフトウェアのインストールが必要とか、

  • AirDropだとApple系OSである必要

があるなどの転送の障壁があって、GUIが使えないデバイスに送りたいときなどは困ってしまいます。

すでにたくさんのファイル共有系のサービスがありますが、コマンドを使ったCUIベースにあまり親切な設計なものはあまりないと思います。

そこで、上記の問題を解決するために、以下のようなファイル転送の仕組みを作りました。



  • 他デバイス間でデータ転送ができ、

  • 別途ソフトウェアのインストール不要で、

  • パイプにとても親和性が高くエンジニアフレンドリー


実際の動作

データの送信者も受信者も転送するためにインストールするものはありません。

curlコマンドやブラウザなどHTTP通信ができるものがあれば転送できます。

以下のデモでは、seq 100000を上のマシンから下のマシンに送っています。

opt.gif

これはseq 100000以外にもcatでもlsでもパイプ渡せるあらゆるものが転送できます。例えば、ファイルが送りたいときはcat ファイル名とします。

以下のように、100MB.datをデバイスをまたいで送れます。

opt.gif

エンドツーエンドの暗号化なども可能です。

パイプが使えるということは、圧縮や暗号化など、また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でも動作可能でした。

以下のデモのようにターミナルからブラウザに画像ファイルを送ることも簡単です。

opt.gif

以下のようにブラウザからもファイルを送ることができ、curlで受け取れます。

opt.gif

まとめると、


  • 従来の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.mlです。この中で一番短いのでコマンドのタイプ数を削減できます。

その他個人的な運用では、LAN内にPiping Serverを立ててLANではより高速かつ閉じた転送するのに使ってます。主にLAN内ではSSHなどが使えない場合、特にLinux<=>Windowsなどで使ってます。

使い方が忘れたときはGET /helpで簡単にヘルプが確認できます。

$ curl https://ppng.ml/help

ヘルプが出力される

サーバーの立てやすさもこだわっていて、


  • ポータブルなバイナリで起動させたり、


  • docker runで起動できたり、

自分用のサーバーを手軽に立てれるようにしてます。サーバーの立て方は、下の方に書きたいと思います。


データ転送の仕組み

HTTPを使います。また、NAT超え問題を解決するために、サーバーが送信者と受信者の経由役になります。おそらくピアツーピア(P2P)のTURNサーバに似たものだと思います。

多くのデバイス・OSでの利用できるようにするためとインストール不要を実現するために、

広く広まっているプロトコルでの通信が不可欠です。そこで通信はHTTP/HTTPSを使います。

CUIからは、curlwgetで簡単にデータを送るのも、受け取るのも可能です。curlwgetは最初からインストールされている事が多いです。

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して予約していたほうが、送信者との安全なコネクションがはれると思います。


  1. GET /mydata => 送信者を待つ

  2. GET /mydata => 拒否されて接続が切断 (すでに待ち状態の人がいるから)

  3. POST /mydata => 1の人に転送される

以下は、より安全なGET firstでの転送の様子です。

opt.gif


複数人に送信

/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

ソース:javascript - Node.js Piping the same readable stream into multiple (writable) targets - Stack Overflow

new PassThough()を作って、そのPassThoughを最初に.pipe()させて、もとのストリームではなくPassThoughのメソッドの.on()や.pipe()を使って処理をしているのが分かると思います。

HTTPサーバーを作るときのhttp.IncomingMessagehttp.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.exeGitHub 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

Alpineベースのイメージのためそこそこ軽量です。

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で自動化して人為ミスなどを減らしています。


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.ml/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)

クライアントは無限にyesyを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.ml/mymsg



受けとる

curl https://ppng.ml/mymsg | zcat



暗号化して送る

openssl aes-256-cbcを使うとパスワードを使って共通鍵暗号方式でエンドツーエンドの暗号化が実現できます。


送る

echo hello | openssl aes-256-cbc | curl -T - https://ppng.ml/mymsg



受けとる

curl https://ppng.ml/mymsg | openssl aes-256-cbc -d



ディレクトリを送る

ポイントは、以下のどちらのコマンドも一時ファイルを作らずにストリームでzipやtar.gzを作って、ディレクトリを転送できるところです。


zipで送る

zip -q -r - ./mydir | curl -T - https://ppng.ml/mydir.zip



tar.gzで送る

tar zfcp - ./mydir | curl -T - https://ppng.ml/mydir.tar.gz



プログレスバーを出しながら送る

pvコマンドを使うと進捗のバーが出てくれます。

汎用的な方法は、以下のようにパイプとパイプの間に挟むことで進捗がわかります。

... | pv | curl -T - ...

opt.gif

ファイルを送りたいときは、以下のようにcatの代わりにpvを使うことでOO/100%の形でバーが見やすくなります。

opt.gif


WindowsでもCUIで転送したい!

PowerShellを使う方法の紹介です。

PowerShellは以下のアイコンのやつです。コマンドプロンプトとは別のアプリケーションで、少なくともWindows 10なら標準で入っているはずです。

PowerShell icon

iwrコマンド(=Invoke-WebRequest標準のエイリアスのはずです)を使うとある程度curlの代わりになります。


./mydata.datを送る

iwr https://ppng.ml/mydata -InFile ./mydata.dat -Method Post



./mydata.datとしてダウンロード

iwr https://ppng.ml/mydata -OutFile mydata.dat



パイプして送る

cat ./hello.txt | iwr -InFile $_ -Method POST https://ppng.ml/mymsg


(PowerShellが使えなくても、ブラウザでファイルの送信・受信が可能です)

PowerShellはWindowsに限ったものではなく、マルチプラットフォームを目指しているようです。

個人的に一番お手軽に試せると思うのがDockerを使った方法です。

以下のコマンドでシェルが立ち上がります。

docker run -it microsoft/powershell


vi系のエディタで送る

viやvimなどのエディタを使って一時ファイルを作らずにPiping Serverに送る方法です。

以下のように、:w !curl -T - ... で書いている内容を送ることができます。

opt.gif


標準入力をリアルタイムに送る

以下のように、cat | curl -T - ....を使うと入力の内容が改行ごとに即時送られます。

opt.gif

(追記: -T -で標準入力を受け取るので、cat |はなくてもOKですね。)


ハッシュ値を計算しながら送る

送りながらハッシュ値を計算することで、より自信をもってデータを転送できます。

これのポイントも同じく、一時ファイルを作らずにストリーミングしながらハッシュ値を計算しているところです。

>(shasum ...)という記法はProcess Substitutionという機能で、bashやzshなどに搭載されています。


ハッシュを計算しながら送る

seq 100000 | tee >(shasum >&2) | curl -T - https://ppng.ml/myseq



ハッシュを計算しながら受けとる

curl https://ppng.ml/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://piping.ml/myvideo

Piping Video Streaming

ポイントは、すべての動画がダウンロードされる前から、再生されているところです。簡易的なストリーミングできています。

これに対応した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.ml/myscript >&/dev/null)


(Macの場合はscript -fの代わりにscript -F)


共有された画面を見る人

curl https://ppng.ml/myscript


/myscriptの箇所は適宜変えて使ってください。

仕組みとしては、scriptコマンドというターミナル作業ログをとるコマンドの力です。

ログをファイルに取れて、Process Substitution>(curl -T -)に向かってログするようにしています。ネットワーク越しにこれをパイプできるPiping serverに渡るため、画面を見る人はただcurl ...するだけで画面を見ることができます。

以下がターミナル画面共有のデモです。

opt.gif

魅力は既存のインストールされているであろうscriptコマンドとcurlだけでターミナル共有が完結するところです。また、色のついた文字やtopslなど動きが激しい画面もうまく共有できています。/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などを主に利用しています。





  1. コマンドで完結するため、広告などでサーバー運営費をまかなえるようなサービスではないので、予告なしにサーバーと止めることがあると思います。サーバーは手軽にたてれるように心がけたので、止まってしまった時はご自身で立ていただければと思います。