【问题标题】:How to safely read ReaderStream from asyncio without breaking the stream如何在不中断流的情况下从 asyncio 安全地读取 ReaderStream
【发布时间】:2018-08-12 20:55:33
【问题描述】:

我正在尝试构建一个中间人代理服务器,它将客户端请求中继到我的应用程序中预定义的各种代理服务。所以,为了做到这一点,我需要区分客户对服务的请求。
只要我不取消注释# data = await client_reader.read(2048),下面的代码就可以工作,我可以从中解析标题。即,如果我执行该代码,
说如果我使用未注释的那行代码来执行此操作:

r = requests.get('http://api.ipify.org/', headers = {'Proxy-Type':'custom'}, proxies={'http':'http://127.0.0.1:9911'}) 

我会在r.content 中从ipify 获得408 Request Time-out

async def proxy_data(reader, writer, connection_string):

  try:
    while True:
      data = await reader.read(2048)
      if not data:
        break
      writer.write(data)
      await writer.drain()
  except Exception as e:
    raise
  finally:
    writer.close()

async def accept_client(client_reader, client_writer):

  try:
    # Get proxy service - [Proxy-Type] from header via client_reader
    # Set remote_address and remote_port based on it

    # data = await client_reader.read(2048)

    (remote_reader, remote_writer) = await asyncio.wait_for(
    asyncio.open_connection(host = remote_address, port = remote_port),
      timeout = 30)
  except asyncio.TimeoutError:
    client_writer.close()
  except Exception as e:
    client_writer.close()
  else:
    # Pipe the streams
    asyncio.ensure_future(proxy_data(client_reader, remote_writer))
    asyncio.ensure_future(proxy_data(remote_reader, client_writer))

def main():

  def handle_client(client_reader, client_writer):
    asyncio.ensure_future(
      accept_client(
        client_reader = client_reader,
        client_writer = client_writer
      )
    )


  loop = asyncio.get_event_loop()
  try:
    server = loop.run_until_complete(
      asyncio.start_server(
        handle_client, host = '127.0.0.1', port = 9911))
  except Exception as e:
    logger.error('Bind error: {}'.format(e))
    sys.exit(1)

  for s in server.sockets:
    logger.debug('Proxy broker listening on {}'.format(s.getsockname()))

  try:
    loop.run_forever()
  except KeyboardInterrupt:
    pass
if __name__ == '__main__':
  main()

谁能指出这里的问题或如何有条件地打开连接?

【问题讨论】:

  • 也许您在管道流之前忘记在远程写入器中写入标头数据?
  • 但是remote_writer不应该依赖于打开的连接吗?也就是说,remote_writer 不应该基于客户端标头中请求的自定义代理服务吗?
  • 是的,但您不想通过提取代理信息来破坏请求格式。您必须找到一种方法来重建可以由远程服务处理的有效请求。
  • 没错,但由于我不希望以任何方式篡改请求,因此只需在 accept_client 中拥有 local_reader 流的 tmp_reader 副本并为健康)状况。仅仅通过复制似乎也行不通,我认为这与它是 coro 有关吗?!
  • 保存数据后使用client_reader.feed_data(data)修复

标签: python tcp proxy python-asyncio


【解决方案1】:

通过重新输入 client_reader (ReaderStream) 解决了这个问题

感谢 Vincent 让 cmets 指出请求被篡改的情况,并将请求重构回原来的形式

async def accept_client(client_reader, client_writer):

  try:
    # Get proxy service - [Proxy-Type] from header via client_reader
    # Set remote_address and remote_port based on it

    data = await client_reader.read(2048)

    # -------- Edited --------

    # perform operations based on data and obtain remote_address, remote_port

    (remote_reader, remote_writer) = await asyncio.wait_for(
    asyncio.open_connection(host = remote_address, port = remote_port),
      timeout = 30)
  except asyncio.TimeoutError:
    client_writer.close()
  except Exception as e:
    client_writer.close()
  else:

    # Write the data to remote
    remote_writer.write(data)
    await remote_writer.drain()

    # Pipe the streams
    asyncio.ensure_future(proxy_data(client_reader, remote_writer))
    asyncio.ensure_future(proxy_data(remote_reader, client_writer))

【讨论】:

  • 其实feed_data是被底层流协议调用的,我觉得这样用不太安全。例如,协议可能会从客户端接收更多数据并在您将标头反馈回来之前将其提供,从而破坏请求。我会改用remote_writer.write(data)(在管道流之前)。
  • 不确定是否可以使用remote_writer 作为我的条件来实际定义remote_addressremote_port 将取决于从local_reader 中的标头获得的数据。比如说,如果客户端标头包含 'Proxy-Type':'tor',那么我的 remote_address 将是 tor ip
  • 当然可以,但这应该不是问题。我将使用以下工作流程:从客户端读取标头,获取远程地址,创建远程连接,将标头写入远程,管道客户端和远程。
  • 太棒了!再次感谢
猜你喜欢
  • 2011-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-26
相关资源
最近更新 更多