vital.vim icon indicating copy to clipboard operation
vital.vim copied to clipboard

RFC: 新しい HTTP 系モジュールの提唱

Open lambdalisue opened this issue 8 years ago • 24 comments

以下の要望を満たす 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.Requesturllib.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)')

lambdalisue avatar Dec 27 '15 18:12 lambdalisue

できれば HTTP だけじゃなくていろいろなの扱いたいですね。Websocket とか

lambdalisue avatar Dec 27 '15 18:12 lambdalisue

(concprocを使って実現できそうな予感力)

ujihisa avatar Dec 27 '15 18:12 ujihisa

(concprocを使って実現できそうな予感力)

curl, wget のあたりはそれがベストっぽいですよね。Pythonは内部でループ・スレッド化など全部やっちゃったほうが良さそう。Async にするところだけ concproc かなぁ

lambdalisue avatar Dec 27 '15 18:12 lambdalisue

困ってるのが既存との関係性をどうするか?って部分です。僕の構想だと使い方変わり過ぎなので良いアイディア募集。

lambdalisue avatar Dec 27 '15 18:12 lambdalisue

その使い方のイメージだと非同期になってないように見えます。(retrieve でブロックする)

thinca avatar Dec 27 '15 21:12 thinca

その使い方のイメージだと非同期になってないように見えます。(retrieve でブロックする)

はい。非同期のイメージはまだありません。読みなおしてみたら「非同期」だけって読めましたね。同期・非同期双方に対応(というか非同期でも可能くらいのプライオリティ)と考えていました。

lambdalisue avatar Dec 28 '15 02:12 lambdalisue

非同期リクエスト自体は元々入れたいと思っていて、非同期さえ入れれば並列は単純に非同期で何度もリクエストを呼べばいいだけなので、非同期機能を入れればいいように思います。 私の中のざっくりとしたイメージだと、settingsasync などを追加し、これが true ならば response を即座に返し、この response は vimproc のように現在の状態や結果を読み込むための関数を持っていればいいのかな、と言う感じです。(本当にざっくりなので詳細は要検討) 並列を前提に結果を待ちたいなら、response の配列を投げたら全部終わるまで待ってくれるヘルパ関数を提供すれば良いでしょう。

thinca avatar Dec 28 '15 03:12 thinca

非同期はとても良いのですが、やはり同期で早いというのも重要かと思っております。 非同期になるとどうしても組みにくくなるので、上記の使い方のような感じでもマルチスレッドなどの恩恵により高速化できると良いなーというのが自分の意見ですね(Web.HTTP をやめて Python で 50 スレッド化したらすごく早くなった経験上)。

なので request オブジェクトという形を取れば同期・非同期、シングル・マルチスレッド問わず使えるかな?というのが感想です(返り値の response は同期・非同期で変わると思いますが、非同期のコールバック内で同期の時と同じ response を使えるようにするなどでいいかな?)

lambdalisue avatar Dec 28 '15 03:12 lambdalisue

私の中では "async" を指定しない限り使い方のイメージは大きく変わらない予定ですが、細かいところが気になっていたのと(settingsresponse の各アイテム名が vital 内では珍しく camelCase だったり、response に関数生やしたかったり)、名前的にも Network.HTTP は妥当っぽいので、作り直しちゃうのはアリかなと思ってます。

thinca avatar Dec 28 '15 03:12 thinca

camelCaseresponse 関数わかる。 個人的に async はオプション指定より関数名ごと変えたい気がします。戻り値が変わるので

lambdalisue avatar Dec 28 '15 03:12 lambdalisue

非同期はとても良いのですが、やはり同期で早いというのも重要かと思っております。

なるほど。つまり、同期で且つ複数のリクエストを並列で投げられれば、内部の実装によっては別々に非同期リクエストを投げるよりかは速く処理できる場合がある、と言うことですね。 そういうことだと複数リクエストを投げるインターフェースはありだと思います。

個人的に async はオプション指定より関数名ごと変えたい気がします。

これもわかる。

thinca avatar Dec 28 '15 03:12 thinca

こんな感じがいいなーと妄想。リクエストオブジェクトにすることで通信のための情報と通信を分けたい。んで複数リクエストを投げるインターフェイスはヘルパー関数としてモジュールに持たせたい(実際はヘルパーレベルではないにせよ)

" 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

lambdalisue avatar Dec 28 '15 03:12 lambdalisue

非同期でやる場合、処理をすすめるのはユーザー側になります。callback は利便性的にはあってもいいですが、自動で呼ばれることはないでしょう。(わかってるかもしれないですが念のため)

thinca avatar Dec 28 '15 03:12 thinca

自動で呼ばれることはないでしょう。

はい。responsevimproc 戻り値のようなものを提供しているという前提で、ついでに autocmd の定義などを行ってくれるヘルパー的な set_callback() 関数もあるといいなという希望です(どんな処理がいいのか僕はわからんのでw)

lambdalisue avatar Dec 28 '15 03:12 lambdalisue

set_callback() は関数を登録するだけにして、自動で autocmd を定義するのは別にした方が良いかと思います。なんなら別モジュールでもいいレベル。

thinca avatar Dec 28 '15 04:12 thinca

自動でautocmdを定義するのはconcprocにも欲しいので、レッツ別

ujihisa avatar Dec 28 '15 04:12 ujihisa

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

ujihisa avatar Dec 28 '15 04:12 ujihisa

ラッパー関数1つ追加するだけですが、 s:H.set_callback(s:H.DONE, ...) はjQueryっぽく s:H.done(...) がいいかなぁと思ったり。 メソッドチェーンも有りで。

tyru avatar Dec 28 '15 04:12 tyru

openとretrieveの分離をなくして単一化しました。

Single と Multiple で内部処理が若干異なる予想なので、個人的には分離した方がいい気がします(Singleでいいのに無駄な処理が走ることになると思うので)。

lambdalisue avatar Dec 28 '15 05:12 lambdalisue

:+1:

kamichidu avatar Dec 31 '15 05:12 kamichidu

一応作りかけ貼っておきます(まだ動かない) https://github.com/lambdalisue/vital-Network-HTTP

lambdalisue avatar Jan 04 '16 09:01 lambdalisue

ここに書いて良いのか分かりませんが、URIのencodeとdecodeはWeb.HTTPのもの使った方がいいんじゃないでしょうか?(わざわざWeb.HTTPからコピペしてるようですが、何でだろう)

tyru avatar Jan 04 '16 16:01 tyru

最終的に置き換えなのかなという理由でした。まあ、まだ動かないので試行錯誤してるとこですが

lambdalisue avatar Jan 04 '16 16:01 lambdalisue

あ、なるほど。

tyru avatar Jan 04 '16 16:01 tyru