Repository: square/okhttp
File: okhttp/src/commonJvmAndroid/kotlin/okhttp3/Cache.kt (Kotlin)

Code before:

    override fun contentLength(): Long = contentLength?.toLongOrDefault(-1L) ?: -1L

    override fun source(): BufferedSource = bodySource
  }

  companion object {
    private const val VERSION = 201105
    private const val ENTRY_METADATA = 0
    private const val ENTRY_BODY = 1
    private const val ENTRY_COUNT = 2

    @JvmStatic
    fun key(url: HttpUrl): String =
      url
        .toString()
        .encodeUtf8()
        .md5()
        .hex()

    internal fun key(request: Request): String =
      if (
        request.method == "QUERY" &&
        request.body != null
      ) {
        key(request.url) + "_" + Buffer().also { request.body.writeTo(it) }.md5().hex()
      } else {
        key(request.url)
      }

    @Throws(IOException::class)
    internal fun readInt(source: BufferedSource): Int {
      try {
        val result = source.readDecimalLong()
        val line = source.readUtf8LineStrict()
        if (result < 0L || result > Integer.MAX_VALUE || line.isNotEmpty()) {
          throw IOException("expected an int but was \"$result$line\"")
        }
        return result.toInt()
      } catch (e: NumberFormatException) {
        throw IOException(e.message)
      }
    }

    /**
     * Returns true if none of the Vary headers have changed between [cachedRequest] and
     * [newRequest].
     */
    fun varyMatches(
      cachedResponse: Response,
      cachedRequest: Headers,

Review comment (performance):
Several comments here:

1. Can we stream the body instead of buffering it in memory entirely? 
2. Slightly concerned that this is more doing I/O than was the case before but this code is followed by reading/writing from the cache, which is also doing I/O so probably OK?
3. Does the key size matter? I was thinking we could hash the url + body all at once to get a single md5: `md5(url + '\0' + body)` or something like so? This way we have all uniform md5 keys. One drawback of course is that we can't tell from looking at a given key whether it is a `QUERY` key or not. Maybe it's an useful property, maybe not, not sure.
4. ~~Actually, it should probably be more like `md5(method + '\0' + url + '\0' + body)` as we don't want a `QUERY` with an empty body to clash with a `GET` of the same url?~~ Well, nevermind, we can't change the existing keys for `GET` so keep using the previous one for GET and `md5(url + '\0' + body)` for QUERY should be good. The `\0` will ensure that there is no clash.

Code after:
        .hex()

    /** Returns the cache key for a request */
    internal fun key(request: Request): String {
      if (
        // Request such as PUT, DELETE that invalidates a GET
        !HttpMethod.invalidatesCache(request.method) &&

        // QUERY request that considers the request body in the cache key
        HttpMethod.isCacheable(request.method) &&
        HttpMethod.permitsRequestBody(request.method) &&
        request.body != null
      ) {
        val hashingSink = HashingSink.md5(blackholeSink())
        hashingSink.buffer().use {
          it.writeUtf8(request.method)
          it.writeByte(0)
          it.writeUtf8(request.url.toString())
          it.writeByte(0)
          request.body.writeTo(it)
        }
        return hashingSink.hash.hex()
      }

      return request.url
        .toString()
        .encodeUtf8()
        .md5()
        .hex()
    }

    @Throws(IOException::class)
    internal fun readInt(source: BufferedSource): Int {
      try {
        val result = source.readDecimalLong()
        val line = source.readUtf8LineStrict()
        if (result < 0L || result > Integer.MAX_VALUE || line.isNotEmpty()) {
          throw IOException("expected an int but was \"$result$line\"")
        }
        return result.toInt()
      } catch (e: NumberFormatException) {
        throw IOException(e.message)
      }
    }

    /**
     * Returns true if none of the Vary headers have changed between [cachedRequest] and
     * [newRequest].
     */
    fun varyMatches(
      cachedResponse: Response,