FileNotFoundError 文件存在时(在当前脚本中创建时)
FileNotFoundError When file exists (when created in current script)
我正在尝试创建一个安全的(例如,SSL/HTTPS)XML-RPC 客户端服务器。当我的系统上存在所需的证书时,客户端-服务器部分可以完美运行;但是,当我在执行期间尝试 create 证书时,即使证书清楚地存在,我也会在打开 ssl-wrapped 套接字时收到 FileNotFoundError (因为前面的函数创建了它们。)
为什么存在文件时会报FileNotFoundError? (如果我只是关闭并重新启动 python 脚本,则在打开套接字时不会产生任何错误,并且一切正常。)
我在别处搜索过解决方案,但我找到的 best/closest 答案可能是 "race conditions" 在创建证书和打开证书之间。但是,我尝试添加 "sleep" 以减轻竞争条件的可能性(以及 运行 通过用户输入菜单单独设置每个功能)每次都出现相同的错误。
我错过了什么?
这是我的代码片段:
import os
import threading
import ssl
from xmlrpc.server import SimpleXMLRPCServer
import certs.gencert as gencert # <---- My python module for generating certs
...
rootDomain = "mydomain"
CERTFILE = "certs/mydomain.cert"
KEYFILE = "certs/mydomain.key"
...
def listenNow(ipAdd, portNum, serverCert, serverKey):
# Create XMLRPC Server, based on ipAdd/port received
server = SimpleXMLRPCServer((ipAdd, portNum))
# **THIS** is what causes the FileNotFoundError ONLY if
# the certificates are created during THE SAME execution
# of the program.
server.socket = ssl.wrap_socket(server.socket,
certfile=serverCert,
keyfile=serverKey,
do_handshake_on_connect=True,
server_side=True)
...
# Start server listening [forever]
server.serve_forever()
...
# Verify Certificates are present; if not present,
# create new certificates
def verifyCerts():
# If cert or key file not present, create new certs
if not os.path.isfile(CERTFILE) or not os.path.isfile(KEYFILE):
# NOTE: This [genert] will create certificates matching
# the file names listed in CERTFILE and KEYFILE at the top
gencert.gencert(rootDomain)
print("Certfile(s) NOT present; new certs created.")
else:
print("Certfiles Verified Present")
# Start a thread to run server connection as a daemon
def startServer(hostIP, serverPort):
# Verify certificates present prior to starting server
verifyCerts()
# Now, start thread
t = threading.Thread(name="ServerDaemon",
target=listenNow,
args=(hostIP,
serverPort,
CERTFILE,
KEYFILE
)
)
t.daemon = True
t.start()
if __name__ == '__main__':
startServer("127.0.0.1", 12345)
time.sleep(60) # <--To allow me to connect w/client before closing
当我 运行 以上内容时,没有证书,这是我收到的错误:
$ python3 test.py
Certfile(s) NOT present; new certs created.
Exception in thread ServerDaemon:
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/usr/lib/python3.5/threading.py", line 862, in run
self._target(*self._args, **self._kwargs)
File "test.py", line 41, in listenNow
server_side=True)
File "/usr/lib/python3.5/ssl.py", line 1069, in wrap_socket
ciphers=ciphers)
File "/usr/lib/python3.5/ssl.py", line 691, in __init__
self._context.load_cert_chain(certfile, keyfile)
FileNotFoundError: [Errno 2] No such file or directory
当我第二次重新运行脚本时(即证书文件在启动时已经存在,一切运行都如预期的那样没有错误,我可以连接我的客户很好。
$ python3 test.py
Certfiles Verified Present
什么阻止 ssl.wrap_socket 函数从 seeing/accessing 刚刚创建的文件(从而产生 FileNotFoundError 异常)?
编辑 1:
感谢约翰·戈登的评论。这是 gencert.py 的副本,由 Atul Varm 提供,可在此处 https://gist.github.com/toolness/3073310
找到
import os
import sys
import hashlib
import subprocess
import datetime
OPENSSL_CONFIG_TEMPLATE = """
prompt = no
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
C = US
ST = IL
L = Chicago
O = Toolness
OU = Experimental Software Authority
CN = %(domain)s
emailAddress = varmaa@toolness.com
[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = %(domain)s
DNS.2 = *.%(domain)s
"""
MYDIR = os.path.abspath(os.path.dirname(__file__))
OPENSSL = '/usr/bin/openssl'
KEY_SIZE = 1024
DAYS = 3650
CA_CERT = 'ca.cert'
CA_KEY = 'ca.key'
# Extra X509 args. Consider using e.g. ('-passin', 'pass:blah') if your
# CA password is 'blah'. For more information, see:
#
# http://www.openssl.org/docs/apps/openssl.html#PASS_PHRASE_ARGUMENTS
X509_EXTRA_ARGS = ()
def openssl(*args):
cmdline = [OPENSSL] + list(args)
subprocess.check_call(cmdline)
def gencert(domain, rootdir=MYDIR, keysize=KEY_SIZE, days=DAYS,
ca_cert=CA_CERT, ca_key=CA_KEY):
def dfile(ext):
return os.path.join('domains', '%s.%s' % (domain, ext))
os.chdir(rootdir)
if not os.path.exists('domains'):
os.mkdir('domains')
if not os.path.exists(dfile('key')):
openssl('genrsa', '-out', dfile('key'), str(keysize))
# EDIT 3: mydomain.key gets output here during execution
config = open(dfile('config'), 'w')
config.write(OPENSSL_CONFIG_TEMPLATE % {'domain': domain})
config.close()
# EDIT 3: mydomain.config gets output here during execution
openssl('req', '-new', '-key', dfile('key'), '-out', dfile('request'),
'-config', dfile('config'))
# EDIT 3: mydomain.request gets output here during execution
openssl('x509', '-req', '-days', str(days), '-in', dfile('request'),
'-CA', ca_cert, '-CAkey', ca_key,
'-set_serial',
'0x%s' % hashlib.md5(domain +
str(datetime.datetime.now())).hexdigest(),
'-out', dfile('cert'),
'-extensions', 'v3_req', '-extfile', dfile('config'),
*X509_EXTRA_ARGS)
# EDIT 3: mydomain.cert gets output here during execution
print "Done. The private key is at %s, the cert is at %s, and the " \
"CA cert is at %s." % (dfile('key'), dfile('cert'), ca_cert)
if __name__ == "__main__":
if len(sys.argv) < 2:
print "usage: %s <domain-name>" % sys.argv[0]
sys.exit(1)
gencert(sys.argv[1])
编辑 2:
关于约翰的评论,"this might mean that those files are being created, but not in the directory [I] expect":
当我在另一个 window 中打开目录时,我看到文件在执行过程中弹出 正确的 位置。此外,当 运行 第二次未更改 test.py
脚本时,文件被识别为存在于正确(相同)的位置。这让我相信文件位置不是问题所在。感谢您的建议。我会继续寻找。
编辑 3:
我单步执行了 gencert.py 程序,每个文件在执行过程中都在正确的时间正确输出。我在上面的文件中指出了它们的确切输出时间,标记为 "EDIT 3"
虽然 gencert 暂停等待我的输入 (raw_input),但我可以 open/view/edit 在另一个程序中毫无问题地提到上述文件。
此外,对于第一个 test.py 实例 运行ning(暂停,等待用户输入,就在 mydomain.cert 出现之后),我可以 运行 一秒钟test.py 在另一个终端中的实例,它 sees/uses 文件就好了。
但是,在第一个实例中,如果我继续执行程序,它会输出 "FileNotFoundError."
上面包含的问题源于John建议的os.chdir(rootdir)
的使用;但是,具体细节与创建的文件位于错误位置略有不同。问题是 运行 程序的当前工作目录 (cwd) 被 gencert()
更改了。以下是具体细节:
程序以 test.py
开始,调用 verifyCerts()
。此时程序在运行所在文件夹test.py
所在的当前目录运行里面。此时使用os.getcwd()
查找当前目录。在这种情况下(作为示例),它是 运行 in:
/home/name/testfolder/
接下来,os.path.isfile()
查找文件 "certs/mydomain.cert" 和 "certs/mydomain.key";根据上面的文件路径 [例如,cwd],它正在寻找以下文件:
/home/name/testfolder/certs/mydomain.cert
/home/name/testfolder/certs/mydomain.key
上面的文件不存在,所以程序执行 gencert.gencert(rootDomain)
,正确地 在上面提到的确切位置创建了两个文件2号。
问题确实是os.chdir()
调用:当gencert()
执行时,它使用os.chdir()
来改变cwd 到 "rootdir," 即 os.path.abspath(os.path.dirname(__file__))
,这是当前文件 (gencert.py) 的目录。由于此文件位于更深的文件夹中,因此新的 cwd 变为:
/home/name/testfolder/certs/
当gencert()
执行完毕,控制returns到test.py
时,cwd不再变化;即使执行 returns 到 test.py
.
,cwd 仍保持为 /home/name/testfolder/certs/
稍后,当 ssl.wrap_socket()
尝试查找 serverCert 和 serverKey 时,它会在 cwd,所以这里是它正在寻找的完整路径:
/home/name/testfolder/certs/certs/mydomain.cert
/home/name/testfolder/certs/certs/mydomain.key
这两个文件 NOT 存在,所以程序正确 returns "FileNotFoundError".
解决方案
A) 将 "gencert.py" 文件移动到与 "test.py"
相同的目录
B) 在"gencert.py"开头添加cwd = os.getcwd()
记录程序原cwd;然后,在最后,添加 os.chdir(cwd)
以在结束并将控制权交还给调用程序之前变回原来的 cwd。
我选择了选项 'B',我的程序现在可以完美运行了。感谢 John Gordon 帮助我找到问题的根源。
我正在尝试创建一个安全的(例如,SSL/HTTPS)XML-RPC 客户端服务器。当我的系统上存在所需的证书时,客户端-服务器部分可以完美运行;但是,当我在执行期间尝试 create 证书时,即使证书清楚地存在,我也会在打开 ssl-wrapped 套接字时收到 FileNotFoundError (因为前面的函数创建了它们。)
为什么存在文件时会报FileNotFoundError? (如果我只是关闭并重新启动 python 脚本,则在打开套接字时不会产生任何错误,并且一切正常。)
我在别处搜索过解决方案,但我找到的 best/closest 答案可能是 "race conditions" 在创建证书和打开证书之间。但是,我尝试添加 "sleep" 以减轻竞争条件的可能性(以及 运行 通过用户输入菜单单独设置每个功能)每次都出现相同的错误。
我错过了什么?
这是我的代码片段:
import os
import threading
import ssl
from xmlrpc.server import SimpleXMLRPCServer
import certs.gencert as gencert # <---- My python module for generating certs
...
rootDomain = "mydomain"
CERTFILE = "certs/mydomain.cert"
KEYFILE = "certs/mydomain.key"
...
def listenNow(ipAdd, portNum, serverCert, serverKey):
# Create XMLRPC Server, based on ipAdd/port received
server = SimpleXMLRPCServer((ipAdd, portNum))
# **THIS** is what causes the FileNotFoundError ONLY if
# the certificates are created during THE SAME execution
# of the program.
server.socket = ssl.wrap_socket(server.socket,
certfile=serverCert,
keyfile=serverKey,
do_handshake_on_connect=True,
server_side=True)
...
# Start server listening [forever]
server.serve_forever()
...
# Verify Certificates are present; if not present,
# create new certificates
def verifyCerts():
# If cert or key file not present, create new certs
if not os.path.isfile(CERTFILE) or not os.path.isfile(KEYFILE):
# NOTE: This [genert] will create certificates matching
# the file names listed in CERTFILE and KEYFILE at the top
gencert.gencert(rootDomain)
print("Certfile(s) NOT present; new certs created.")
else:
print("Certfiles Verified Present")
# Start a thread to run server connection as a daemon
def startServer(hostIP, serverPort):
# Verify certificates present prior to starting server
verifyCerts()
# Now, start thread
t = threading.Thread(name="ServerDaemon",
target=listenNow,
args=(hostIP,
serverPort,
CERTFILE,
KEYFILE
)
)
t.daemon = True
t.start()
if __name__ == '__main__':
startServer("127.0.0.1", 12345)
time.sleep(60) # <--To allow me to connect w/client before closing
当我 运行 以上内容时,没有证书,这是我收到的错误:
$ python3 test.py
Certfile(s) NOT present; new certs created.
Exception in thread ServerDaemon:
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/usr/lib/python3.5/threading.py", line 862, in run
self._target(*self._args, **self._kwargs)
File "test.py", line 41, in listenNow
server_side=True)
File "/usr/lib/python3.5/ssl.py", line 1069, in wrap_socket
ciphers=ciphers)
File "/usr/lib/python3.5/ssl.py", line 691, in __init__
self._context.load_cert_chain(certfile, keyfile)
FileNotFoundError: [Errno 2] No such file or directory
当我第二次重新运行脚本时(即证书文件在启动时已经存在,一切运行都如预期的那样没有错误,我可以连接我的客户很好。
$ python3 test.py
Certfiles Verified Present
什么阻止 ssl.wrap_socket 函数从 seeing/accessing 刚刚创建的文件(从而产生 FileNotFoundError 异常)?
编辑 1: 感谢约翰·戈登的评论。这是 gencert.py 的副本,由 Atul Varm 提供,可在此处 https://gist.github.com/toolness/3073310
找到import os
import sys
import hashlib
import subprocess
import datetime
OPENSSL_CONFIG_TEMPLATE = """
prompt = no
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
C = US
ST = IL
L = Chicago
O = Toolness
OU = Experimental Software Authority
CN = %(domain)s
emailAddress = varmaa@toolness.com
[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = %(domain)s
DNS.2 = *.%(domain)s
"""
MYDIR = os.path.abspath(os.path.dirname(__file__))
OPENSSL = '/usr/bin/openssl'
KEY_SIZE = 1024
DAYS = 3650
CA_CERT = 'ca.cert'
CA_KEY = 'ca.key'
# Extra X509 args. Consider using e.g. ('-passin', 'pass:blah') if your
# CA password is 'blah'. For more information, see:
#
# http://www.openssl.org/docs/apps/openssl.html#PASS_PHRASE_ARGUMENTS
X509_EXTRA_ARGS = ()
def openssl(*args):
cmdline = [OPENSSL] + list(args)
subprocess.check_call(cmdline)
def gencert(domain, rootdir=MYDIR, keysize=KEY_SIZE, days=DAYS,
ca_cert=CA_CERT, ca_key=CA_KEY):
def dfile(ext):
return os.path.join('domains', '%s.%s' % (domain, ext))
os.chdir(rootdir)
if not os.path.exists('domains'):
os.mkdir('domains')
if not os.path.exists(dfile('key')):
openssl('genrsa', '-out', dfile('key'), str(keysize))
# EDIT 3: mydomain.key gets output here during execution
config = open(dfile('config'), 'w')
config.write(OPENSSL_CONFIG_TEMPLATE % {'domain': domain})
config.close()
# EDIT 3: mydomain.config gets output here during execution
openssl('req', '-new', '-key', dfile('key'), '-out', dfile('request'),
'-config', dfile('config'))
# EDIT 3: mydomain.request gets output here during execution
openssl('x509', '-req', '-days', str(days), '-in', dfile('request'),
'-CA', ca_cert, '-CAkey', ca_key,
'-set_serial',
'0x%s' % hashlib.md5(domain +
str(datetime.datetime.now())).hexdigest(),
'-out', dfile('cert'),
'-extensions', 'v3_req', '-extfile', dfile('config'),
*X509_EXTRA_ARGS)
# EDIT 3: mydomain.cert gets output here during execution
print "Done. The private key is at %s, the cert is at %s, and the " \
"CA cert is at %s." % (dfile('key'), dfile('cert'), ca_cert)
if __name__ == "__main__":
if len(sys.argv) < 2:
print "usage: %s <domain-name>" % sys.argv[0]
sys.exit(1)
gencert(sys.argv[1])
编辑 2: 关于约翰的评论,"this might mean that those files are being created, but not in the directory [I] expect":
当我在另一个 window 中打开目录时,我看到文件在执行过程中弹出 正确的 位置。此外,当 运行 第二次未更改 test.py
脚本时,文件被识别为存在于正确(相同)的位置。这让我相信文件位置不是问题所在。感谢您的建议。我会继续寻找。
编辑 3: 我单步执行了 gencert.py 程序,每个文件在执行过程中都在正确的时间正确输出。我在上面的文件中指出了它们的确切输出时间,标记为 "EDIT 3"
虽然 gencert 暂停等待我的输入 (raw_input),但我可以 open/view/edit 在另一个程序中毫无问题地提到上述文件。
此外,对于第一个 test.py 实例 运行ning(暂停,等待用户输入,就在 mydomain.cert 出现之后),我可以 运行 一秒钟test.py 在另一个终端中的实例,它 sees/uses 文件就好了。
但是,在第一个实例中,如果我继续执行程序,它会输出 "FileNotFoundError."
上面包含的问题源于John建议的os.chdir(rootdir)
的使用;但是,具体细节与创建的文件位于错误位置略有不同。问题是 运行 程序的当前工作目录 (cwd) 被 gencert()
更改了。以下是具体细节:
程序以
test.py
开始,调用verifyCerts()
。此时程序在运行所在文件夹test.py
所在的当前目录运行里面。此时使用os.getcwd()
查找当前目录。在这种情况下(作为示例),它是 运行 in:/home/name/testfolder/
接下来,
os.path.isfile()
查找文件 "certs/mydomain.cert" 和 "certs/mydomain.key";根据上面的文件路径 [例如,cwd],它正在寻找以下文件:/home/name/testfolder/certs/mydomain.cert
/home/name/testfolder/certs/mydomain.key
上面的文件不存在,所以程序执行
gencert.gencert(rootDomain)
,正确地 在上面提到的确切位置创建了两个文件2号。问题确实是
os.chdir()
调用:当gencert()
执行时,它使用os.chdir()
来改变cwd 到 "rootdir," 即os.path.abspath(os.path.dirname(__file__))
,这是当前文件 (gencert.py) 的目录。由于此文件位于更深的文件夹中,因此新的 cwd 变为:/home/name/testfolder/certs/
当
gencert()
执行完毕,控制returns到test.py
时,cwd不再变化;即使执行 returns 到test.py
. ,cwd 仍保持为 稍后,当
ssl.wrap_socket()
尝试查找 serverCert 和 serverKey 时,它会在 cwd,所以这里是它正在寻找的完整路径:/home/name/testfolder/certs/certs/mydomain.cert
/home/name/testfolder/certs/certs/mydomain.key
这两个文件 NOT 存在,所以程序正确 returns "FileNotFoundError".
/home/name/testfolder/certs/
解决方案
A) 将 "gencert.py" 文件移动到与 "test.py"
相同的目录B) 在"gencert.py"开头添加cwd = os.getcwd()
记录程序原cwd;然后,在最后,添加 os.chdir(cwd)
以在结束并将控制权交还给调用程序之前变回原来的 cwd。
我选择了选项 'B',我的程序现在可以完美运行了。感谢 John Gordon 帮助我找到问题的根源。