V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
lon91ong
V2EX  ›  Python

求教转发本地应用网络请求的方法

  •  
  •   lon91ong · 2022-03-31 19:37:34 +08:00 · 3279 次点击
    这是一个创建于 991 天前的主题,其中的信息可能已经有所发展或是发生改变。

    程序目标

    用本地服务代替网络服务, 使得需要依赖网络运行的一款第三方应用能够脱机运行。

    已实现的阶段目标

    使用 RESTful 架构的 falcon 包实现了基本的网络请求响应,在依赖的资源文件完整的情况下,程序可以脱机运行。 但是遇到资源文件缺失,需要通过网络下载补足的情况就会报错。

    问题描述

    本地文件请求:GET http://127.0.0.1:8686/Download/example.zip

    文件远程真实地址:GET http://down.domain.com/Download/example.zip

    用什么方法能在 python 程序接收到本地文件请求时,由 python重定向连接到文件远程真实地址,并将响应数据原样发回给本地第三方应用。类似于本地应用通过代理的方式取得了文件。

    用关键词redirect在 Github 搜索了一些 python 项目,没找到特别适用的。

    希望大侠不吝赐教,万分感谢。

    第 1 条附言  ·  2022-04-18 22:06:51 +08:00

    最终使用方案, 感谢 @ec0 大侠的不吝赐教

        url ='http://down.domain.com/Download/中文文件名示例.zip'
        #中文文件名编码问题,参见https://github.com/Pylons/waitress/issues/318
        resp.downloadable_as = '中文文件名示例.zip'.encode("utf-8").decode("latin1") 
        #设置响应Headers: 'Content-Length', 'Content-Type'
        resp.set_headers(dict(list(requests.head(url).headers.items())[:2])) 
        chunk = lambda u: (yield from requests.get(u,stream=True).iter_content(chunk_size=8192))
        resp.stream = chunk(url)
    
    26 条回复    2022-04-17 16:51:59 +08:00
    lizenghui
        1
    lizenghui  
       2022-03-31 19:50:15 +08:00
    nginx 可以啊。
    lon91ong
        2
    lon91ong  
    OP
       2022-03-31 19:53:23 +08:00
    @lizenghui 有没有相近的应用实例呢? nginx 似乎时个网站后台框架吧? 只玩过 RESTful 类的简单的
    seakingii
        3
    seakingii  
       2022-03-31 19:57:01 +08:00
    @lon91ong 搜索 nginx 反向代理
    cheng6563
        4
    cheng6563  
       2022-03-31 20:02:56 +08:00
    你收到请求后直接改下地址然后 urllib.request 发请求出去就行了啊,把收到的数据直接返回或者临时存盘再返回都行。HTTP 文件下载有几个特殊 Header 表示文件名什么的,你响应的时候填上就行。
    cheng6563
        5
    cheng6563  
       2022-03-31 20:04:49 +08:00   ❤️ 1
    你先试试写一个提供文件下载接口,随便返回点数据就行。
    再写一个用 urllib.request 下载远程文件的用例。
    最后把两个方法整合下就行了。
    Puteulanus
        6
    Puteulanus  
       2022-03-31 20:17:38 +08:00
    听起来像个 cache proxy ,squid transparent proxy 是不是就能做
    xuxuxu123
        7
    xuxuxu123  
       2022-03-31 20:26:54 +08:00
    如果远程文件的真实地址是确定的,那么 nginx 反向代理就可以了
    ClericPy
        8
    ClericPy  
       2022-03-31 20:43:07 +08:00
    这说的是 gost 么... https://github.com/ginuerzh/gost

    或者用 Python 随手写个端口转发? https://github.com/ClericPy/ichrome/issues/84

    如果我理解错了, 就当没看到我
    lon91ong
        9
    lon91ong  
    OP
       2022-03-31 21:00:04 +08:00 via Android
    @seakingii 有点牛刀杀鸡的赶脚
    lon91ong
        10
    lon91ong  
    OP
       2022-03-31 21:02:51 +08:00 via Android
    @cheng6563 这就是我想要的思路, 还需要找例程实验一番,多谢指点迷津
    lon91ong
        11
    lon91ong  
    OP
       2022-03-31 21:33:31 +08:00 via Android
    @cheng6563 遇到资源文件比较大的情况,四五十 M 的大小, 没法等下载完了再响应, 这种情况怎么处理呢?
    lon91ong
        12
    lon91ong  
    OP
       2022-03-31 21:52:31 +08:00
    @Puteulanus 感谢提供关键词 squid, 找到个相关的项目, https://github.com/GlobalRadio/squid-redirect
    简单看了看 readme, 似乎用得上, 明天再看看
    mingl0280
        13
    mingl0280  
       2022-03-31 23:43:08 +08:00
    ……你就不会检查一下 file exist 然后不存在的文件自动下载了返回么?
    Tink
        14
    Tink  
       2022-03-31 23:51:39 +08:00 via Android
    很多办法,上面说的 nginx 反代可以,也可以在你的程序里判断本地文件是否存在,不存在就去下载
    biubiuF
        15
    biubiuF  
       2022-04-01 00:40:54 +08:00
    iptables redirect
    ec0
        16
    ec0  
       2022-04-01 03:10:59 +08:00
    试试这段代码

    ```
    class ProxyResource:
    ____def file_generator(self, url):
    ________with requests.get(url, stream=True) as r:
    ____________for chunk in r.iter_content(chunk_size=8192):
    ________________yield chunk


    ____def on_get(self, req, resp):
    ________resp.downloadable_as = 'example.zip'
    ________resp.stream = self.file_generator('http://down.domain.com/Download/example.zip')
    ```
    lon91ong
        17
    lon91ong  
    OP
       2022-04-01 09:11:55 +08:00
    @ec0 太感谢了! 感激涕零了要!!!
    另外遇到一点小麻烦, 不知道时这个方法的问题, 还是我用的 WSGI 标准的 waitress 包的问题,
    在转发时没有转发 headers 信息, 文件长度、编码等信息全都丢失了
    需要用什么方法补救呢?
    enrolls
        18
    enrolls  
       2022-04-01 11:26:17 +08:00
    这个需求就跟在腾讯云买了同主机和 cos 一个道理,主机跟 cos 免流量通信,cos 直接下载需要流量。主机返回 cos 的 body 出去。headers 可以一并 yield ,之后自己组装
    lon91ong
        19
    lon91ong  
    OP
       2022-04-01 11:52:41 +08:00
    @enrolls headers 一并 yield 要如何操作? yield 只能返回一个对象吧???
    ec0
        20
    ec0  
       2022-04-01 12:01:27 +08:00
    这样呢?

    ```
    def on_get(self, req, resp):
    ____resp.downloadable_as = 'example.zip'
    ____r = requests.head('http://down.domain.com/Download/example.zip')
    ____resp.content_length = r.headers['content-length']
    ____resp.content_type = r.headers['content-type']
    ____resp.stream = self.file_generator('http://down.domain.com/Download/example.zip')
    ```
    lon91ong
        21
    lon91ong  
    OP
       2022-04-01 12:21:58 +08:00
    @ec0 好了, 真的感激涕零了
    macrorules
        22
    macrorules  
       2022-04-01 12:46:42 +08:00
    iptables -A PREROUTING -t nat -i eth1 -d 127.0.0.1:8686 -j DNAT --to-destination ${down.domain.com 的地址}
    lon91ong
        23
    lon91ong  
    OP
       2022-04-02 22:41:51 +08:00 via Android
    @macrorules 这个操作不是只能在路由器上搞吗??程序里面没听说过啊!
    macrorules
        24
    macrorules  
       2022-04-02 23:18:12 +08:00
    @lon91ong 这是在本地(你的电脑,笔记本)上操作的
    lon91ong
        25
    lon91ong  
    OP
       2022-04-03 20:49:25 +08:00
    @macrorules 应该还要加上 Linux 系统的限制吧, Windows 没有 iptables 命令
    lon91ong
        26
    lon91ong  
    OP
       2022-04-17 16:51:59 +08:00
    使用 lambda 替代了 genreat 函数:
    ```
    url ='http://down.domain.com/Download/example.zip'
    resp.downloadable_as=labfile.encode("utf-8").decode("latin1") #编码问题,参见 https://github.com/Pylons/waitress/issues/318
    r = requests.head(url)
    resp.content_length = r.headers['content-length']
    resp.content_type = r.headers['content-type']
    chunk = lambda u: (yield from requests.get(u,stream=True).iter_content(chunk_size=8192))
    resp.stream = chunk(url)
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3139 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 04:54 · PVG 12:54 · LAX 20:54 · JFK 23:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.