vital.vim
vital.vim copied to clipboard
RFC: 新しい HTTP 系モジュールの提唱
以下の要望を満たす HTTP 系モジュールがほしい
- 複数の HTTP リクエストを並列処理(page=1 -> page=100 とかを Vim のループでやるのは辛いので)
- 非同期処理も可能(IO時間かかるので、ただ必須じゃない)
で、今考えているのが
- curl, wget の場合は Concproc などを利用し複数プロセスを立ち上げて並列処理
- python の場合はマルチスレッドによる並列処理 (python/python3)
- 上記はどちらも sync/async 対応可能かな?
- Vim からは
request
オブジェクトをリスト形式で渡す - 各クライアントは
response
オブジェクトをリスト形式で返す - 通信エラーは
response
オブジェクトで返す + オプションにて通信エラー発生段階ですべての残り処理を無視(できたらいいな)
なんですが既存の Web.HTTP
と使い方が大幅に変わりそうなのでモジュールの名前(Network.HTTP
とか?)や使い方のイメージなどのいろいろな意見を募集します。
以下は今の段階での僕の中の使い方イメージ。複数ページにまたがるAPIから全エントリーを取得する場合と想定。Python の urllib.request.Request
と urllib.request.urlopen
の関係性みたいな
let s:N = s:V.import('Network.HTTP')
" Web.HTTP.request() 相当だが実際にはリクエストしないで request オブジェクトを返す
" https://github.com/vim-jp/vital.vim/blob/master/doc/vital-web-http.txt#L33-L35
let requests = [
\ s:N.request('url'),
\ s:N.request('method', 'url', {'settings': 'settings'}),
\ s:N.request({'settings': 'settings'}),
\]
" 与えられた request オブジェクトにしたがって実際の HTTP アクセス(curl, wget, python などで)
" Vim のループで処理ではなく外部プロセスでループを処理したい(高速化用に)
" 非同期処理のノウハウが無いので、同期処理の場合のイメージ
let responses = s:N.retrieve(requests, {'client': ['python', 'wget', 'curl']})
" 戻り値を処理して全エントリーを取得(本当は status とかでフィルタしたりする)
let entries = map(responses, 's:J.decode(v:val.content)')
できれば HTTP だけじゃなくていろいろなの扱いたいですね。Websocket とか
(concprocを使って実現できそうな予感力)
(concprocを使って実現できそうな予感力)
curl, wget のあたりはそれがベストっぽいですよね。Pythonは内部でループ・スレッド化など全部やっちゃったほうが良さそう。Async にするところだけ concproc かなぁ
困ってるのが既存との関係性をどうするか?って部分です。僕の構想だと使い方変わり過ぎなので良いアイディア募集。
その使い方のイメージだと非同期になってないように見えます。(retrieve
でブロックする)
その使い方のイメージだと非同期になってないように見えます。(retrieve でブロックする)
はい。非同期のイメージはまだありません。読みなおしてみたら「非同期」だけって読めましたね。同期・非同期双方に対応(というか非同期でも可能くらいのプライオリティ)と考えていました。
非同期リクエスト自体は元々入れたいと思っていて、非同期さえ入れれば並列は単純に非同期で何度もリクエストを呼べばいいだけなので、非同期機能を入れればいいように思います。
私の中のざっくりとしたイメージだと、settings
に async
などを追加し、これが true ならば response を即座に返し、この response は vimproc のように現在の状態や結果を読み込むための関数を持っていればいいのかな、と言う感じです。(本当にざっくりなので詳細は要検討)
並列を前提に結果を待ちたいなら、response の配列を投げたら全部終わるまで待ってくれるヘルパ関数を提供すれば良いでしょう。
非同期はとても良いのですが、やはり同期で早いというのも重要かと思っております。 非同期になるとどうしても組みにくくなるので、上記の使い方のような感じでもマルチスレッドなどの恩恵により高速化できると良いなーというのが自分の意見ですね(Web.HTTP をやめて Python で 50 スレッド化したらすごく早くなった経験上)。
なので request
オブジェクトという形を取れば同期・非同期、シングル・マルチスレッド問わず使えるかな?というのが感想です(返り値の response は同期・非同期で変わると思いますが、非同期のコールバック内で同期の時と同じ response を使えるようにするなどでいいかな?)
私の中では "async" を指定しない限り使い方のイメージは大きく変わらない予定ですが、細かいところが気になっていたのと(settings
や response
の各アイテム名が vital 内では珍しく camelCase
だったり、response
に関数生やしたかったり)、名前的にも Network.HTTP
は妥当っぽいので、作り直しちゃうのはアリかなと思ってます。
camelCase
と response
関数わかる。
個人的に async
はオプション指定より関数名ごと変えたい気がします。戻り値が変わるので
非同期はとても良いのですが、やはり同期で早いというのも重要かと思っております。
なるほど。つまり、同期で且つ複数のリクエストを並列で投げられれば、内部の実装によっては別々に非同期リクエストを投げるよりかは速く処理できる場合がある、と言うことですね。 そういうことだと複数リクエストを投げるインターフェースはありだと思います。
個人的に async はオプション指定より関数名ごと変えたい気がします。
これもわかる。
こんな感じがいいなーと妄想。リクエストオブジェクトにすることで通信のための情報と通信を分けたい。んで複数リクエストを投げるインターフェイスはヘルパー関数としてモジュールに持たせたい(実際はヘルパーレベルではないにせよ)
" Sync ================================================================
let request = s:H.request(...)
" Single
let response = request.open()
" Multiple
" Vim で複数処理するのではなく外部で処理して高速化
let responses = s:H.retrieve([request, request, request])
" Async ===============================================================
" Single
let async_response = request.open_async()
" Multiple
" Vimで複数処理するのではなく外部(ry
let async_responses = s:H.retrieve_async([request, request, request])
" 終了時の callback を登録。すでに終わっていた場合は登録しないで呼び出し
" 他にも 'progress' とかそういう系の callback もほしい
call async_response.set_callback('done', 's:open_done')
call async_responses.set_callback('done', 's:retrieve_done')
function! s:open_done(response, request) abort
" response 使って処理
endfunction
function! s:retrieve_done(responses, requests) abort
" responses 使って処理
endfunction
非同期でやる場合、処理をすすめるのはユーザー側になります。callback は利便性的にはあってもいいですが、自動で呼ばれることはないでしょう。(わかってるかもしれないですが念のため)
自動で呼ばれることはないでしょう。
はい。response
は vimproc
戻り値のようなものを提供しているという前提で、ついでに autocmd の定義などを行ってくれるヘルパー的な set_callback()
関数もあるといいなという希望です(どんな処理がいいのか僕はわからんのでw)
set_callback()
は関数を登録するだけにして、自動で autocmd を定義するのは別にした方が良いかと思います。なんなら別モジュールでもいいレベル。
自動でautocmdを定義するのはconcprocにも欲しいので、レッツ別
request.open()なくてもいいんじゃないか説の支持者です。 openとretrieveの分離をなくして単一化しました。 同時に、一貫性のためメソッド的な辞書関数をなくしました。
" Sync ================================================================
let request = s:H.request(...)
" Single
let [response] = s.H:retrieve([request])
" Multiple
let [resp1, resp2, resp3] = s:H.retrieve([request1, request2, request3])
" NOTE: some of resp[1:3] may be failure object instead of response object
" Async ===============================================================
" Single
let [async_response_f] = s:H.retrieve_async([request])
" Multiple
let [resp1_f, resp2_f, resp3_f] = s:H.retrieve_async([request1, request2, request3])
" ^ ここでいうfはfutureのつもり
" 終了時の callback を登録。すでに終わっていた場合は登録しないで呼び出し
" 他にも 'progress' とかそういう系の callback もほしい
call s:H.set_callback(s:H.DONE, async_response_f, 's:retrieve_done')
call s:H.set_callback(s:H.DONE, resp1_f, 's:retrieve_done')
call s:H.set_callback(s:H.DONE, [resp1_f, resp2_f, resp3_f], 's:retrieve_done_all')
function! s:retrieve_done(request, response) abort
" response 使って処理
endfunction
function! s:retrieve_done(requests, responses) abort
" responses 使って処理
endfunction
ラッパー関数1つ追加するだけですが、 s:H.set_callback(s:H.DONE, ...) はjQueryっぽく s:H.done(...) がいいかなぁと思ったり。 メソッドチェーンも有りで。
openとretrieveの分離をなくして単一化しました。
Single と Multiple で内部処理が若干異なる予想なので、個人的には分離した方がいい気がします(Singleでいいのに無駄な処理が走ることになると思うので)。
:+1:
一応作りかけ貼っておきます(まだ動かない) https://github.com/lambdalisue/vital-Network-HTTP
ここに書いて良いのか分かりませんが、URIのencodeとdecodeはWeb.HTTPのもの使った方がいいんじゃないでしょうか?(わざわざWeb.HTTPからコピペしてるようですが、何でだろう)
最終的に置き換えなのかなという理由でした。まあ、まだ動かないので試行錯誤してるとこですが
あ、なるほど。