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
vast0906
V2EX  ›  Python

BaseHTTPServer 库中如何获取客户端请求 server 端的域名是什么?

  •  
  •   vast0906 · 2019-09-03 22:23:49 +08:00 · 2050 次点击
    这是一个创建于 1688 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在 py2 下找到了

    self.path
    self.client_address
    self.command
    self.requestline
    self.request_version
    

    请问我该如何在代码中获取 client 访问 server 的域名是什么? 比如我访问的是 v2ex.com/test。怎么能获取到 v2ex.com 这段域名?

    历史原因暂时只能用 py2

    14 条回复    2019-09-04 12:27:26 +08:00
    vast0906
        1
    vast0906  
    OP
       2019-09-03 22:34:36 +08:00
    目前在群里根据大佬提示通过查找 headers 找到的一种解法,还有其他的办法吗?
    lcdtyph
        2
    lcdtyph  
       2019-09-03 22:48:06 +08:00
    @vast0906
    只有 http 的话没有了,就算是查找 header 也不是一定能拿到域名的,因为 http 协议没有规定必须专递那几个 header,也没有要求传输域名。
    但是如果有 tls 的话可以在 tls 这一层拿到访问的域名,因为 client hello 的 sni 部分携带了请求的域名。
    ochatokori
        3
    ochatokori  
       2019-09-03 23:23:38 +08:00 via Android
    http 头里面的 host 就是啊
    also24
        4
    also24  
       2019-09-03 23:41:28 +08:00
    @lcdtyph #2
    HTTP 1.1 的标准里,header 里的 host 字段是必须的

    参见 RFC: https://tools.ietf.org/html/rfc2616#page-129

    A client MUST include a Host header field in all HTTP/1.1 request messages .
    lcdtyph
        5
    lcdtyph  
       2019-09-03 23:46:41 +08:00
    @also24
    SimpleHttpServer 只实现了 HTTP 1.0 吧,至少我本地这个版本是这样的,如果发送 1.1 的请求,得到的回复第一行也是
    HTTP/1.0 200 OK
    binux
        6
    binux  
       2019-09-04 00:03:40 +08:00 via Android
    @lcdtyph #5 即使 SimpleHTTPServer 只实现了 1.0 这和发送方有什么关系呢?发送的时候他又不知道对方是 1.0 的。
    also24
        7
    also24  
       2019-09-04 00:07:33 +08:00
    @lcdtyph #5
    根据我从下面的代码看到的情况,是可以支持 1.1 的(确切来说,是支持 1.1 的 keepalive 特性),
    不过需要手动设置一下 protocol_version ( # Set this to HTTP/1.1 to enable automatic keepalive )
    https://github.com/python/cpython/blob/2.7/Lib/BaseHTTPServer.py#L270
    https://github.com/python/cpython/blob/2.7/Lib/BaseHTTPServer.py#L513

    另外,host 字段是否存在,其实主要取决于你发起请求的客户端使用的版本啊,你 1.1 的请求已经自带 host header 了。


    当然,假如你一定要考虑 1.0 的请求,其实也有一定可能能从 URI 里读取到的:
    https://tools.ietf.org/html/rfc1945#page-24
    翻到这里发现一件事,那就是 1.0 时,如果这样读不到,其它的 Web Server 也读不到,不用纠结。
    lcdtyph
        8
    lcdtyph  
       2019-09-04 00:14:47 +08:00
    @binux @also24
    但是一个恶意的客户端是可以发送非正常请求的,这种情况下 simple server 就是拿不到 header 而且不会在 handle 请求之前返回错误。
    如果只使用 SimpleHTTPServer 那么就是没有“一定”能拿到域名的方法,一定要拿到域名我只能想到套一层 tls。要么就在 handle 请求的时候拿不到域名就手动返回错误好了。
    binux
        9
    binux  
       2019-09-04 00:22:10 +08:00
    @lcdtyph 你考虑非正常请求干嘛?读不到 host 就返回 400 就好了呗。
    如果我在前面放个 cdn,你不发送 host,难不成还要靠猜吗?
    also24
        10
    also24  
       2019-09-04 00:25:47 +08:00
    @lcdtyph #8
    一个 “恶意” 的客户端,如何做到:
    让 Web Server 拿到正确的域名(从而正常分发 vhost )
    却又
    不让 Web Application 拿到正确的域名(从而无法取出 host 字段)

    请注意:
    只传递正确的 SNI,但传递错误的 host 字段,不但会欺骗 Web Application,
    事实上在 Web Server 也会被欺骗,此时就已经跑到错误的 vhost 去了。

    另外如果采取 SNI 的方式,你的 SSL 负载要靠 Web Server 来卸载 还是靠 SimpleHTTPServer 来卸载呢?
    靠 Web Server 来卸载的话,哪儿来的 SNI 信息呢?靠 SimpleHTTPServer 卸载的话,好像不支持吧?
    以及,我甚至不需要考虑恶意的客户端,对于 IE6 IE7 这种不支持 SNI 的浏览器,是不是又变成了拿不到了呢?

    其实并不是:SimpleHTTPServer 没有“一定”能拿到域名的方法。
    而应当是:任何的 Web Server 都没有 没有“一定”能拿到域名的方法。


    在当前的环境下,我认为 HTTP 1.1 可以当做事实上的最优选择。
    lcdtyph
        11
    lcdtyph  
       2019-09-04 00:46:22 +08:00
    @also24
    只支持 http1.1 的 server 的确是可以的。我本来的想法是,本应在下层就可以 abort 掉的错误就不要扔给上层处理,比如这种场景下只使用 SimpleHTTPServer 是做不到这点的,是我莫名预设了条件,my bad。
    ---
    只传递正确的 SNI,但是不传递 host 字段,这种错误在 web server 反代时直接就返回 400 了,不需要 http 的上层来处理这种问题;另外 ssl 卸载靠 web server,反代的时候直接把 sni 信息塞进一个 header 转发给 simplehttpserver 就行了,因为 webserver 是可信的。我以为 lz 就是不想在 web app 里 handle 这种事情才来问的,所以以为需求是如果进入了 web app 就一定要能拿到域名。
    also24
        12
    also24  
       2019-09-04 01:11:19 +08:00   ❤️ 1
    @lcdtyph #11
    首先我觉得你预设了楼主认为 “一定拿到真实 host ” 的需求,我没有看到楼主要求这个。
    楼主问这个的原因,我猜测是因为 BaseHTTPServer 没有 self.host,让楼主有些懵如何自己获取。

    相对应的,例如 flask 有 flask.Request.host,django 有 django.http.HttpRequest.get_host,都可以直接取。
    https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request.host
    https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.get_host

    那么他们都是怎么取的呢?我比较懒,只翻了 flask,层层追溯可以找到这里:
    https://github.com/pallets/werkzeug/blob/master/src/werkzeug/wsgi.py#L145

    而 django,其实上面贴的文档里已经讲了,相比直接去 host 字段,还考虑了反代的场景。


    至于:ssl 卸载靠 web server,反代的时候直接把 sni 信息塞进一个 header 转发给 simplehttpserver 就行了。
    不妨调研一下,想要实现这样的效果,应该怎样去具体实现,怕不是要写个 nginx 插件才行?


    最后,关于 “ webserver 是可信的”,不妨看一下著名的 WebServer Nginx 是怎样处理请求的:
    http://nginx.org/en/docs/http/request_processing.html

    我想,你应该能看到大量的 ' “ Host ” header field ' 这样的字眼,我就不多说了。
    conn4575
        13
    conn4575  
       2019-09-04 03:58:10 +08:00 via Android
    根据 http 协议标准,这个只能从 header 里获取,虽然绝大部分情况都能拿的到,但是协议本身没有强制要求,例如如果用 curl 请求的话就没有
    vast0906
        14
    vast0906  
    OP
       2019-09-04 12:27:26 +08:00
    @also24

    @binux

    @lcdtyph

    @conn4575

    睡过头了。谢谢各位的回复。提问的时候大部分原因是 @also24 猜的那样因为 BaseHTTPServer 没有 self.host,让我有些懵如何自己获取。后来发现 headers 里面去获取,觉得不够优雅。所以就提问了。。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5306 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 03:44 · PVG 11:44 · LAX 20:44 · JFK 23:44
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.