ssize_t Http2Stream::Provider::FD::OnRead(nghttp2_session* handle,
                                          int32_t id,
                                          uint8_t* buf,
                                          size_t length,
                                          uint32_t* flags,
                                          nghttp2_data_source* source,
                                          void* user_data) {
  Http2Session* session = static_cast<Http2Session*>(user_data);
  Http2Stream* stream = session->FindStream(id);

  DEBUG_HTTP2SESSION2(session, "reading outbound file data for stream %d", id);
  CHECK_EQ(id, stream->id());

  int fd = source->fd;
  int64_t offset = stream->fd_offset_;
  ssize_t numchars = 0;

  if (stream->fd_length_ >= 0 &&
      stream->fd_length_ < static_cast<int64_t>(length))
    length = stream->fd_length_;

  uv_buf_t data;
  data.base = reinterpret_cast<char*>(buf);
  data.len = length;

  uv_fs_t read_req;

  if (length > 0) {
    // TODO(addaleax): Never use synchronous I/O on the main thread.
    numchars = uv_fs_read(session->event_loop(),
                          &read_req,
                          fd, &data, 1,
                          offset, nullptr);
    uv_fs_req_cleanup(&read_req);
  }

  // Close the stream with an error if reading fails
  if (numchars < 0)
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;

  // Update the read offset for the next read
  stream->fd_offset_ += numchars;
  stream->fd_length_ -= numchars;

  DEBUG_HTTP2SESSION2(session, "sending %d bytes", numchars);

  // if numchars < length, assume that we are done.
  if (static_cast<size_t>(numchars) < length || length <= 0) {
    DEBUG_HTTP2SESSION2(session, "no more data for stream %d", id);
    *flags |= NGHTTP2_DATA_FLAG_EOF;
    session->GetTrailers(stream, flags);
    // If the stream or session gets destroyed during the GetTrailers
    // callback, check that here and close down the stream
    if (stream->IsDestroyed())
      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
    if (session->IsDestroyed())
      return NGHTTP2_ERR_CALLBACK_FAILURE;
  }

  return numchars;
}
ssize_t Http2Stream::Provider::FD::OnRead(nghttp2_session* handle,
                                          int32_t id,
                                          uint8_t* buf,
                                          size_t length,
                                          uint32_t* flags,
                                          nghttp2_data_source* source,
                                          void* user_data) {
  Http2Session* session = static_cast<Http2Session*>(user_data);
  Http2Stream* stream = session->FindStream(id);

  DEBUG_HTTP2SESSION2(session, "reading outbound file data for stream %d", id);
  CHECK_EQ(id, stream->id());

  int fd = source->fd;
  int64_t offset = stream->fd_offset_;
  ssize_t numchars = 0;

  if (stream->fd_length_ >= 0 &&
      stream->fd_length_ < static_cast<int64_t>(length))
    length = stream->fd_length_;

  uv_buf_t data;
  data.base = reinterpret_cast<char*>(buf);
  data.len = length;

  uv_fs_t read_req;

  if (length > 0) {
     
    numchars = uv_fs_read(session->event_loop(),
                          &read_req,
                          fd, &data, 1,
                          offset, nullptr);
    uv_fs_req_cleanup(&read_req);
  }

   
  if (numchars < 0)
    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;

   
  stream->fd_offset_ += numchars;
  stream->fd_length_ -= numchars;

  DEBUG_HTTP2SESSION2(session, "sending %d bytes", numchars);

   
  if (static_cast<size_t>(numchars) < length || length <= 0) {
    DEBUG_HTTP2SESSION2(session, "no more data for stream %d", id);
    *flags |= NGHTTP2_DATA_FLAG_EOF;
    session->GetTrailers(stream, flags);
     
     
    if (stream->IsDestroyed())
      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
    if (session->IsDestroyed())
      return NGHTTP2_ERR_CALLBACK_FAILURE;
  }

  return numchars;
}