std.net.curlのプログレスメータが出る問題について

こんにちは。
今日は、[@alpha_kai_NETさんも報告されているstd.net.curlのプログレスメータの問題](http://qiita.com/alpha_kai_NET/items/e96c0106cd3b79948288)について、問題の原因と対策について話します。

また最後に書いていますが、どうかこの記事を見た人、報告お願いします。


## 問題の概要

次のコードをコンパイルして実行してみてください。標準出力にcurlの進捗状況を表すプログレスメータが出力されると思います。

~~~~~~~~~~d
import std.net.curl;

void main()
{
    auto http = HTTP();
    http = http.dup();      // ここを消すと無くなる
    get("dlang.org", http);
}
~~~~~~~~~~~

このように`HTTP.dup()`によりインスタンスを複製した場合、プログレスメータが表示されてしまいます。

そのため、`byLineAsync`などのように内部で`.dup()`を呼び出している標準ライブラリの関数を使うと、なぜかコンソールにプログレスメータが表示されてしまいます。


## 問題の詳細

問題の原因は[`std.net.curl.Curl.dup()`メソッドのこの行](https://github.com/D-Programming-Language/phobos/blob/master/std/net/curl.d#L3636)です。以下に`.dup()`メソッドを引用させてもらいます。

~~~~~~~~~~~~~~~~~d
/**
   Duplicate this handle.
   The new handle will have all options set as the one it was duplicated
   from. An exception to this is that all options that cannot be shared
   across threads are reset thereby making it safe to use the duplicate
   in a new thread.
*/
Curl dup()
{
    Curl copy;
    copy.handle = curl_easy_duphandle(handle);
    copy.stopped = false;

    with (CurlOption) {
        auto tt = TypeTuple!(file, writefunction, writeheader,
                             headerfunction, infile,
                             readfunction, ioctldata, ioctlfunction,
                             seekdata, seekfunction, sockoptdata,
                             sockoptfunction, opensocketdata,
                             opensocketfunction, noprogress,
                             progressdata, progressfunction,
                             debugdata, debugfunction,
                             interleavedata,
                             interleavefunction, chunk_data,
                             chunk_bgn_function, chunk_end_function,
                             fnmatch_data, fnmatch_function,
                             cookiejar, postfields);
        foreach(option; tt)
            copy.clear(option);
    }

    // The options are only supported by libcurl when it has been built
    // against certain versions of OpenSSL - if your libcurl uses an old
    // OpenSSL, or uses an entirely different SSL engine, attempting to
    // clear these normally will raise an exception
    copy.clearIfSupported(CurlOption.ssl_ctx_function);
    copy.clearIfSupported(CurlOption.ssh_keydata);

    // Enable for curl version > 7.21.7
    static if (LIBCURL_VERSION_MAJOR >= 7 &&
               LIBCURL_VERSION_MINOR >= 21 &&
               LIBCURL_VERSION_PATCH >= 7)
    {
        copy.clear(CurlOption.closesocketdata);
        copy.clear(CurlOption.closesocketfunction);
    }

    copy.set(CurlOption.nosignal, 1);

    // copy.clear(CurlOption.ssl_ctx_data); Let ssl function be shared
    // copy.clear(CurlOption.ssh_keyfunction); Let key function be shared

    /*
      Allow sharing of conv functions
      copy.clear(CurlOption.conv_to_network_function);
      copy.clear(CurlOption.conv_from_network_function);
      copy.clear(CurlOption.conv_from_utf8_function);
    */

    return copy;
}
~~~~~~~~~~~~~~~~~~

`with`文内が問題の原因です。`Curl.clear(option)`というメソッドは、ポインタを設定するオプション`option`に`null`値を設定するメソッドです。しかし、`alias tt`のリストを見ると、`noprogress`が含まれています。

`noprogress`は[cURLのドキュメント](http://curl.haxx.se/libcurl/c/CURLOPT_NOPROGRESS.html)を見る限り、`0`か`1`の`long`型を設定するオプションのはずなので、`Curl.clear(CurlOption.noprogress)`は誤りです。

この誤りにより、`HTTP.dup()`の呼び出しで`Curl.dup()`が呼び出され、`Curl.clear(CurlOption.noprogress)`により`curl_easy_setopt(curlHandle, CURLOPT_NOPROGRESS, null)`されます。`null`は整数では`0`ですから、`CURLOPT_NOPROGRESS`に`0`が設定されてしまい、結果的にプログレスメータが表示されてしまいます。


## 問題の解決方法

`alias tt`のオプションのリストから`noprogress`を消すことで解決できます。

もし、Phobosのコードの改変を待てない場合は次のような型を`std.net.curl.HTTP`の代わりに使うことでその場しのぎをすることができます。

~~~~~~~~~~~~~~~d
struct BugFixedHTTP
{
    static BugFixedHTTP opCall(const(char)[] url)
    {
        return BugFixedHTTP(HTTP(url));
    }


    static BugFixedHTTP opCall()
    {
        return BugFixedHTTP(HTTP());
    }


    static BugFixedHTTP opCall(HTTP http)
    {
        //this.http = http;
        BugFixedHTTP bfhttp;
        bfhttp.http = http;
        return bfhttp;
    }


    BugFixedHTTP dup()
    {
        HTTP conn = http.dup;
        conn.handle.set(CurlOption.noprogress, 1);
        return BugFixedHTTP(conn);
    }


    string encoding() @property 
    {
        return http.tupleof[0].charset;
    }


    HTTP http;
    alias http this;
}
~~~~~~~~~~~~~~~~


## さいごに

この記事を書いた理由は僕にはこの内容を英語を用いて説明する実力が無いためです。訓練だと思えば良いのでしょうが、どうしても自信がないので、誰か代わりにBugzillaに報告してGitHubにPull Request投げてもらえますか？

よろしくお願いします。