Home > Web Front-end > JS Tutorial > body text

Why can't response.body().string() be called multiple times?

亚连
Release: 2018-06-13 10:31:02
Original
2582 people have browsed it

I believe everyone has used or come into contact with OkHttp. When I was using Okhttp recently, I stepped on a pit. I will share it here so that everyone can bypass it when they encounter similar problems in the future.

Just a solution Problems are not enough. This article will focus on analyzing the root of the problem from the source code perspective, which is full of useful information.

1. Found the problem

#During development, I initiated a request by constructing the OkHttpClient object and added it to the queue. After the server responded, The Callback interface triggers the onResponse() method, and then uses the Response object to process the return results and implement business logic in this method. The code is roughly as follows:

//注:为聚焦问题,删除了无关代码
getHttpClient().newCall(request).enqueue(new Callback() {
  @Override
  public void onFailure(Call call, IOException e) {}
  @Override
  public void onResponse(Call call, Response response) throws IOException {
    if (BuildConfig.DEBUG) {
      Log.d(TAG, "onResponse: " + response.body().toString());
    }
    //解析请求体
    parseResponseStr(response.body().string());
  }
});
Copy after login

In onResponse(), for the convenience of debugging, I printed the return body, and then parsed the return body through the parseResponseStr() method (note: response.body() is called twice here. string() ).

This code, which seems to have no problem, actually has a problem after running: through the console, it can be seen that the return body data (json) is successfully printed, but then an exception is thrown:

java.lang.IllegalStateException: closed
Copy after login
Copy after login

2. Solve the problem

After checking the code, I found that the problem lies in calling parseResponseStr() and using response.body().string again. () as parameter. Because I was in a hurry, I checked online and found that response.body().string() can only be called once, so I modified the logic in the onResponse() method and solved the problem:

getHttpClient().newCall(request).enqueue(new Callback() {
  @Override
  public void onFailure(Call call, IOException e) {}
  @Override
  public void onResponse(Call call, Response response) throws IOException {
    //此处,先将响应体保存到内存中
    String responseStr = response.body().string();
    if (BuildConfig.DEBUG) {
      Log.d(TAG, "onResponse: " + responseStr);
    }
    //解析请求体
    parseReponseStr(responseStr);
  }
});
Copy after login

3. Combined with the source code analysis problem

After the problem is solved, it still needs to be analyzed afterwards. Since my previous understanding of OkHttp was limited to its use, and I had not carefully analyzed the details of its internal implementation, I took the time to look down on it over the weekend and figured out the cause of the problem.

Let’s first analyze the most intuitive question: Why can response.body().string() only be called once?

Disassembly, first get the ResponseBody object (it is an abstract class, we don’t need to care about the specific implementation class here) through response.body(), and then call the string() method of ResponseBody to get the response body content.

After analysis, there is no problem with the body() method. Let’s look down at the string() method:

public final String string() throws IOException {
 return new String(bytes(), charset().name());
}
Copy after login

It’s very simple. The byte() method returns the byte[ by specifying the character set (charset). ] The array is converted into a String object. There is no problem with the construction. Continue to look at the byte() method:

public final byte[] bytes() throws IOException {
 //...
 BufferedSource source = source();
 byte[] bytes;
 try {
  bytes = source.readByteArray();
 } finally {
  Util.closeQuietly(source);
 }
 //...
 return bytes;
}
//... 表示删减了无关代码,下同。
Copy after login

In the byte() method, read the byte[] array through the BufferedSource interface object and return it. Combined with the exception mentioned above, I noticed the Util.closeQuietly() method in the finally code block. excuse me? Close silently? ? ?

This method looks weird. Is it right? Follow up and have a look:

public static void closeQuietly(Closeable closeable) {
 if (closeable != null) {
  try {
   closeable.close();
  } catch (RuntimeException rethrown) {
   throw rethrown;
  } catch (Exception ignored) {
  }
 }
}
Copy after login

It turns out that the BufferedSource interface mentioned above can be understood as a resource buffer according to the code documentation comments. Implemented the Closeable interface and closed and released resources by overriding the close() method. Then look down to see what the close() method does (in the current scenario, the BufferedSource implementation class is RealBufferedSource):

//持有的 Source 对象
public final Source source;
@Override
public void close() throws IOException {
 if (closed) return;
 closed = true;
 source.close();
 buffer.clear();
}
Copy after login

Obviously, close and release the resource through source.close(). Speaking of which, the function of the closeQuietly() method is self-evident, which is to close the BufferedSource interface object held by the ResponseBody subclass.

Analysis at this point, we suddenly realize: when we call response.body().string() for the first time, OkHttp returns the buffer resources of the response body and calls the closeQuietly() method to silently release the resources.

In this way, when we call the string() method again, we still return to the byte() method above. This time the problem lies in the bytes = source.readByteArray() line of code. Let’s take a look at the readByteArray() method of RealBufferedSource:

@Override
public byte[] readByteArray() throws IOException {
 buffer.writeAll(source);
 return buffer.readByteArray();
}
Copy after login

Continue to look at the writeAll() method:

@Override
public long writeAll(Source source) throws IOException {
  //...
  long totalBytesRead = 0;
  for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) {
   totalBytesRead += readCount;
  }
  return totalBytesRead;
}
Copy after login

The problem lies in the source.read() of the for loop. Remember when analyzing the close() method above, it called source.close() to close and release the resource. So, what happens when the read() method is called again:

@Override
public long read(Buffer sink, long byteCount) throws IOException {
  //...
  if (closed) throw new IllegalStateException("closed");
  //...
  return buffer.read(sink, toRead);
}
Copy after login

At this point, it meets the crash I encountered earlier:

java.lang.IllegalStateException: closed
Copy after login
Copy after login

4.OkHttp Why is it designed like this?

By fuc*ing the source code, we found the root of the problem, but I still have a question: Why is OkHttp designed this way?

In fact, the best way to understand this problem is to view the annotation documentation of ResponseBody, as JakeWharton responded in issues:

reply of JakeWharton in okhttp issues
Copy after login

In a simple sentence: It's documented on ResponseBody. So I ran to read the class annotation documentation, and finally summarized it as follows:

在实际开发中,响应主体 RessponseBody 持有的资源可能会很大,所以 OkHttp 并不会将其直接保存到内存中,只是持有数据流连接。只有当我们需要时,才会从服务器获取数据并返回。同时,考虑到应用重复读取数据的可能性很小,所以将其设计为 一次性流(one-shot) ,读取后即 '关闭并释放资源'。

5.总结

最后,总结以下几点注意事项,划重点了:

1.响应体只能被使用一次;

2.响应体必须关闭:值得注意的是,在下载文件等场景下,当你以 response.body().byteStream() 形式获取输入流时,务必通过 Response.close() 来手动关闭响应体。

3.获取响应体数据的方法:使用 bytes() 或 string() 将整个响应读入内存;或者使用 source() , byteStream() , charStream() 方法以流的形式传输数据。

4.以下方法会触发关闭响应体:

Response.close()
Response.body().close()
Response.body().source().close()
Response.body().charStream().close()
Response.body().byteString().close()
Response.body().bytes()
Response.body().string()
Copy after login

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在Javascript中如何实现网页抢红包

详细解读ES6语法中可迭代协议

详细解读在React组件“外”如何使用父组件

微信小程序如何实现涂鸦

The above is the detailed content of Why can't response.body().string() be called multiple times?. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template