<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> I have searched the <a href="https://github.com/apache/dubbo/issues">issues</a> of this repository and believe that this is not a duplicate.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> I have checked the <a href="https://github.com/apache/dubbo/blob/master/FAQ.md">FAQ</a> of this repository and believe that this is not a duplicate.</li>
</ul>
<h3 dir="auto">Environment</h3>
<ul dir="auto">
<li>Dubbo version: xxx</li>
<li>Operating System version: xxx</li>
<li>Java version: xxx</li>
</ul>
<h3 dir="auto">Steps to reproduce this issue</h3>
<ol dir="auto">
<li>xxx</li>
<li>xxx</li>
<li>xxx</li>
</ol>
<p dir="auto">Pls. provide [GitHub address] to reproduce this issue.</p>
<h3 dir="auto">Expected Result</h3>
<p dir="auto">What do you expected from the above steps？</p>
<h3 dir="auto">Actual Result</h3>
<p dir="auto">What actually happens?</p>
<p dir="auto">If there is an exception, please attach the exception trace:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="Just put your stack trace here!"><pre class="notranslate"><code class="notranslate">Just put your stack trace here!
</code></pre></div>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I have searched the <a href="https://github.com/apache/incubator-dubbo/issues">issues</a> of this repository and believe that this is not a duplicate.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I have checked the <a href="https://github.com/apache/incubator-dubbo/blob/master/FAQ.md">FAQ</a> of this repository and believe that this is not a duplicate.</li>
</ul>
<h3 dir="auto">Environment</h3>
<ul dir="auto">
<li>Dubbo version: 2.7.1-SNAPSHOT</li>
</ul>
<p dir="auto"><code class="notranslate">org.apache.dubbo.common.utils.IOUtils</code> 这个类看起来像是一个公共类，但实际上里面有坑，不要在你自己的项目里面使用这个类！</p>
<h3 dir="auto">问题描述</h3>
<p dir="auto">该类有一个 write() 方法，内容如下：</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="public static long write(InputStream is, OutputStream os, int bufferSize) throws IOException {
    int read;
    long total = 0;
    byte[] buff = new byte[bufferSize];
    while (is.available() &gt; 0) {   // !!!
        read = is.read(buff, 0, buff.length);
        if (read &gt; 0) {
            os.write(buff, 0, read);
            total += read;
        }
    }
    return total;
}"><pre class="notranslate"><code class="notranslate">public static long write(InputStream is, OutputStream os, int bufferSize) throws IOException {
    int read;
    long total = 0;
    byte[] buff = new byte[bufferSize];
    while (is.available() &gt; 0) {   // !!!
        read = is.read(buff, 0, buff.length);
        if (read &gt; 0) {
            os.write(buff, 0, read);
            total += read;
        }
    }
    return total;
}
</code></pre></div>
<p dir="auto">其中的 while 循环使用了 <code class="notranslate">InputStream.available()</code> 的返回值来作为结束判断条件，这个方法在网络环境当中是不能作为读取结束的依据的，因为当网络传输出现延迟时，该方法立刻会返回 0，导致循环结束。测试代码如下：</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="@Test
public void testWriteURL() throws Exception {
    String url = &quot;https://pan.baidu.com/static/images/16new/bg2.jpg&quot;;
    long expectedSize = 77_949L;

    InputStream inputStream = new URL(url).openStream();
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    long writeSize = org.apache.dubbo.common.utils.IOUtils.write(inputStream, bos);
    assertThat(writeSize, equalTo(expectedSize)); // org.hamcrest.MatcherAssert
}"><pre class="notranslate"><code class="notranslate">@Test
public void testWriteURL() throws Exception {
    String url = "https://pan.baidu.com/static/images/16new/bg2.jpg";
    long expectedSize = 77_949L;

    InputStream inputStream = new URL(url).openStream();
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    long writeSize = org.apache.dubbo.common.utils.IOUtils.write(inputStream, bos);
    assertThat(writeSize, equalTo(expectedSize)); // org.hamcrest.MatcherAssert
}
</code></pre></div>
<p dir="auto">这个测试通常会失败，<code class="notranslate">write()</code> 方法在读取了部分内容后就结束循环并返回了。</p>
<p dir="auto">正确的写法请参考 <code class="notranslate">org.apache.commons.io.IOUtils#copy(java.io.InputStream, java.io.OutputStream)</code> 的实现，对应的测试方法如下：</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="public static void main(String[] args) throws IOException {
    String url = &quot;https://pan.baidu.com/static/images/16new/bg2.jpg&quot;;
    long expectedSize = 77_949L;

    InputStream inputStream = new URL(url).openStream();
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    long writeSize = org.apache.commons.io.IOUtils.copy(inputStream, bos);
    assertThat(writeSize, equalTo(expectedSize)); // org.hamcrest.MatcherAssert
}"><pre class="notranslate"><code class="notranslate">public static void main(String[] args) throws IOException {
    String url = "https://pan.baidu.com/static/images/16new/bg2.jpg";
    long expectedSize = 77_949L;

    InputStream inputStream = new URL(url).openStream();
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    long writeSize = org.apache.commons.io.IOUtils.copy(inputStream, bos);
    assertThat(writeSize, equalTo(expectedSize)); // org.hamcrest.MatcherAssert
}
</code></pre></div>
<p dir="auto">上面这个方法就不会运行失败。</p>
<p dir="auto">为什么这个问题一直没有暴露出来呢？因为这个方法在整个 dubbo 项目中只有一个地方用到了：<code class="notranslate">org.apache.dubbo.common.io.Bytes#unzip()</code>，而这个方法只用来处理内存中的字节串，而与网络无关。万一 dubbo 项目将来在某个地方用它来读取网络内容，问题就会暴露出来。</p>