【问题标题】:Posting data using urllib without SSL certificate validation在没有 SSL 证书验证的情况下使用 urllib 发布数据
【发布时间】:2017-11-10 05:36:38
【问题描述】:

我需要将数据发布到 REST 接口,但是接收主机使用的是自签名证书(这不会改变),所以我需要忽略我收到的明显的证书验证错误。

我的初始脚本如下所示:

from urllib.request import Request, urlopen
from urllib.parse import urlencode

post_url = "https://myserver.mydomain.com:30005/myapipath"
post_payload = { "event": { "Title": "Something, sometime, something, Python"} }
post_headers = {'Content-Type': 'application/xml'}

omi_post = Request(url=post_url, data=post_payload, headers=post_headers)

urlopen(omi_post)

如前所述,这会生成以下堆栈跟踪:

Traceback(最近一次调用最后一次):文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 1318 行,在 do_open 中 encode_chunked=req.has_header('Transfer-encoding')) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\http\client.py", 第 1239 行,应要求提供 self._send_request(method, url, body, headers, encode_chunked) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\http\client.py", 第 1285 行,在 _send_request 中 self.endheaders(body, encode_chunked=encode_chunked) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\http\client.py", 第 1234 行,在 endheaders 中 self._send_output(message_body, encode_chunked=encode_chunked) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\http\client.py", 第 1026 行,在 _send_output 中 self.send(msg) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\http\client.py", 第 964 行,在发送中 self.connect() 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\http\client.py", 第 1400 行,连接中 server_hostname=server_hostname) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\ssl.py", 第 407 行,在 wrap_socket 中 _context=self, _session=session) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\ssl.py", 第 814 行,在 init 中 self.do_handshake() 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\ssl.py", 第 1068 行,在 do_handshake 中 self._sslobj.do_handshake() 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\ssl.py", 第 689 行,在 do_handshake 中 self._sslobj.do_handshake() ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] 证书验证失败 (_ssl.c:777)

在处理上述异常的过程中,又发生了一个异常:

Traceback(最近一次调用最后一次):文件“send_data.py”,第 16 行,在 urlopen(post_it_already) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 223 行,在 urlopen 中 return opener.open(url, data, timeout) File "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 526 行,打开 response = self._open(req, data) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 544 行,在 _open '_open', req) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 504 行,在 _call_chain 结果 = func(*args) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 1361 行,在 https_open context=self._context, check_hostname=self._check_hostname) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 1320 行,在 do_open 中 引发 URLError(err) urllib.error.URLError:

所以我找到了另一篇关于添加 SSL 上下文的帖子,我做了如下操作:

from urllib.request import Request, urlopen
from urllib.parse import urlencode
import ssl

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

post_url = "https://myserver.mydomain.com:30005/myapipath"
post_payload = { "event": { "Title": "Something, sometime, something, Python"} }
post_headers = {'Content-Type': 'application/xml'}

post_it_already = Request(url=post_url, data=post_payload, headers=post_headers)

urlopen(post_it_already, context=ctx)

但是,这会生成以下堆栈跟踪(无论我如何挥舞拳头):

Traceback(最近一次调用最后一次):文件“send_data.py”,第 15 行,在 urlopen(post_it_already, context=ctx) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 223 行,在 urlopen 中 return opener.open(url, data, timeout) File "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 526 行,打开 response = self._open(req, data) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 544 行,在 _open '_open', req) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 504 行,在 _call_chain 结果 = func(*args) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 1361 行,在 https_open context=self._context, check_hostname=self._check_hostname) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\urllib\request.py", 第 1318 行,在 do_open 中 encode_chunked=req.has_header('Transfer-encoding')) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\http\client.py", 第 1239 行,应要求提供 self._send_request(method, url, body, headers, encode_chunked) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\http\client.py", 第 1285 行,在 _send_request 中 self.endheaders(body, encode_chunked=encode_chunked) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\http\client.py", 第 1234 行,在 endheaders 中 self._send_output(message_body, encode_chunked=encode_chunked) 文件 "C:\Users\myusername\AppData\Local\Programs\Python\Python36\lib\http\client.py", 第 1064 行,在 _send_output + b'\r\n' TypeError: can't concat str to bytes

我不知道为什么它抱怨将字符串与字节对象连接起来。我认为它不喜欢我的字符串类型 URL 和我的数据类型有效负载和标头?但是我不确定下一步该做什么。

或者这是一个典型的案例,有人拿 2 个单独的示例代码并期望它们一起工作,而实际上它们就像花生和泡泡糖一样?

【问题讨论】:

    标签: python python-3.x ssl urllib urlopen


    【解决方案1】:

    您的post_payload 的类型应该是“bytes”而不是“str”。

    来自Python docs

    类 urllib.request.Request ...

    ...

    对于 HTTP POST 请求方法,数据应该是标准 application/x-www-form-urlencoded 格式的缓冲区。 urllib.parse.urlencode() 函数采用 2 元组的映射或序列,并以这种格式返回一个 ASCII 字符串。在用作数据参数之前,应将其编码为字节。

    ...

    您可以使用.encode() 方法将post_payload 转换为字节,详见this Python urllib howto

    这与 SSL 验证问题是分开的,但可能是因为它试图 POST 数据,所以成功建立了 HTTPS 连接。

    【讨论】:

    • 谢谢,成功了。我陷入了盲目地从其他代码 sn-ps 构建我的代码的常见陷阱。我应该先阅读文档。我首先添加了使用过的 .parse 对其进行 URL 编码,然后添加了 .encode 以将其转换为字节,完全按照您链接到的文档。
    【解决方案2】:

    您可以将 @contextlib.contextmanager 装饰器与 requests 模块一起使用。这是一种更简单的方法。

    下面的代码可以正常工作

    @contextlib.contextmanager
    def no_ssl_verification():
        old_request = requests.Session.request
        requests.Session.request = partialmethod(old_request,verify=False)
    
        warnings.filterwarnings('ignore', 'Unverified HTTPS request')
        yield
    
        warnings.resetwarnings()
        requests.Session.request = old_request
    

    上述代码的作用是生成带有“verify=False”的requests.Session.request 的部分方法,并过滤任何带有“Unverified Https request”的警告。

    with no_ssl_verification:
       request = requests.post( 
                               url,
                               json={
                                        'name' : some_name
                                    },auth(username,password)
                               )
    

    希望对您有所帮助,如果您找到更好的解决方案,请告诉我。

    【讨论】:

      猜你喜欢
      • 2013-01-11
      • 1970-01-01
      • 2016-08-04
      • 1970-01-01
      • 2021-02-10
      • 2016-10-07
      • 1970-01-01
      • 2021-10-23
      • 2015-04-24
      相关资源
      最近更新 更多