#254 Python 应用: 简易 FTP 服务器
Python FTP PythonSimpleServer 2018-05-07FTP 需要 pyftpdlib 包的支持。
coding in a complicated world
FTP 需要 pyftpdlib 包的支持。
在当前目录起 HTTP 服务,可以用于测试和临时性的文件下载服务。
# Default bind to 0.0.0.0:8000
python -m SimpleHTTPServer
# Maybe you want to use port 8080
python -m SimpleHTTPServer 8080
除了可以指定端口,还可以指定绑定地址、工作目录。
# Also bind to 0.0.0.0:8000
python -m http.server
python -m http.server -h
# usage: server.py [-h] [--cgi] [--bind ADDRESS] [--directory DIRECTORY] [port]
#
# positional arguments:
# port Specify alternate port [default: 8000]
#
# optional arguments:
# -h, --help show this help message and exit
# --cgi Run as CGI Server
# --bind ADDRESS, -b ADDRESS
# Specify alternate bind address [default: all interfaces]
# --directory DIRECTORY, -d DIRECTORY
# Specify alternative directory [default:current directory]
python -m http.server 9999
python -m http.server --bind=127.0.0.1
python -m http.server --bind=127.0.0.1 9999
python -m http.server -d ~/Pictures
如果可以的话,使用 redis-cli monitor
命令来输出所有 Redis 命令也很方便。
有时,条件不允许,或者 Redis 需要处理其他的连接,我希望将自己代码调用的 Redis 命令输出到日志中,方便调试。
相关的文章:
邮件是由纯文本组成,其详细的格式有很多 RFC 规范需要遵守。我这里只能对我所了解的,也是基础的 —— 或者说最核心的 —— 格式做一个说明。
最核心的部分是 1982 年的 RFC 822 (STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES),之后又做过一些更新, 比如 RFC 2822 和 RFC 5322 (Internet Message Format) 和一堆补丁更新。本文要讲的基本格式,从开始到现在并没有什么明显变化。
邮件是一种纯文本格式,最开始只包含 ASCII 字符,后来引入了 MIME 之后,可以制定别的编码,比如 UTF-8 等。
换行符是 \r\n
,也就是 CR
+ LF
。
整体来说,一封邮件由邮件头(Headers)和邮件体(Payload)组成。
邮件头包含若干个头字段
邮件头和邮件体之间用一个空行隔开
RFC2882 和 RFC5322 都规定了电子邮件每一行的长度,排除行末 CRLF,不可以超过 998 个字符,建议不超过 78 个字符。
There are two limits that this specification places on the number of
characters in a line. Each line of characters MUST be no more than
998 characters, and SHOULD be no more than 78 characters, excluding
the CRLF.
如果太长,应该拆分成多行,下一行行首加上至少一个空格或者制表符,表示是上一行的延续。
From: Bob <bob@markjour.com>
To: Mark <mark@markjour.com>
Subject: Hello
Hello, Bob,
Would you like to join me for dinner?
--
Mark
; ( Octal, Decimal.)
# 字符
CHAR = <any ASCII character> ; ( 0-177, 0.-127.)
# 字母
ALPHA = <any ASCII alphabetic character>
; (101-132, 65.- 90.)
; (141-172, 97.-122.)
# 数字
DIGIT = <any ASCII decimal digit> ; ( 60- 71, 48.- 57.)
# 控制字符
CTL = <any ASCII control ; ( 0- 37, 0.- 31.)
character and DEL> ; ( 177, 127.)
# 回车
CR = <ASCII CR, carriage return> ; ( 15, 13.)
# 换行
LF = <ASCII LF, linefeed> ; ( 12, 10.)
# 空格
SPACE = <ASCII SP, space> ; ( 40, 32.)
# 制表符
HTAB = <ASCII HT, horizontal-tab> ; ( 11, 9.)
# 引号
<"> = <ASCII quote mark> ; ( 42, 34.)
# 回车换行
CRLF = CR LF
# 空白
LWSP-char = SPACE / HTAB ; semantics = SPACE
# 连贯空白, 折行空白
linear-white-space = 1*([CRLF] LWSP-char) ; semantics = SPACE
; CRLF => folding
# 特殊字符
specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted-
/ "," / ";" / ":" / "\" / <"> ; string, to use
/ "." / "[" / "]" ; within a word.
# 分隔符
delimiters = specials / linear-white-space / comment
# 文本
text = <any CHAR, including bare ; => atoms, specials,
CR & bare LF, but NOT ; comments and
including CRLF> ; quoted-strings are
; NOT recognized.
# 原子字符
atom = 1*<any CHAR except specials, SPACE and CTLs>
quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or
; quoted chars.
qtext = <any CHAR excepting <">, ; => may be folded
"\" & CR, and including
linear-white-space>
domain-literal = "[" *(dtext / quoted-pair) "]"
dtext = <any CHAR excluding "[", ; => may be folded
"]", "\" & CR, & including
linear-white-space>
# 注释
comment = "(" *(ctext / quoted-pair / comment) ")"
ctext = <any CHAR excluding "(", ; => may be folded
")", "\" & CR, & including
linear-white-space>
quoted-pair = "\" CHAR ; may quote any char
phrase = 1*word ; Sequence of words
word = atom / quoted-string
对应上 ASCII:
0 - 31 控制字符, 其中包括常用的:
- HT ( 9) 水平制表符
- LF (10) 换行
- CR (13) 回车
32 空格
33 - 47 符号 !"#$%&'()*+,-./
48 - 57 数字
58 - 64 符号 :;<=>?@
65 - 90 大写字母
91 - 96 符号 [\]^_`
97 - 122 小写字母
123 - 126 符号 {|}~
127 控制字符(DEL)
CHAR 0-127
CTL 0-37 + 127
符号中:
()<>[]@,;:\".
13 个被视作特殊字符,需要转义
!#$%&'*+-/=?^_`
15 个就是普通符号
atom = 数字 + 字母 + 普通符号
field = field-name ":" [ field-body ] CRLF
field-name = 1*<any CHAR, excluding CTLs, SPACE, and ":">
field-body = field-body-contents
[CRLF LWSP-char field-body]
field-body-contents =
<the ASCII characters making up the field-body, as
defined in the following sections, and consisting
of combinations of atom, quoted-string, and
specials tokens, or else consisting of texts>
字段名称允许使用的字符范围非常宽泛,而且大小写不敏感,
但是一般实践中:
Received
MTA 轨迹(传输过程中的相关信息)Date
发信时间,格式:Fri, 21 Nov 1997 09:55:06 -0600
Sender
Mail From 地址From
发件人Subject
邮件标题To
收件人Cc
抄送Bcc
密送Reply-To
回复地址Message-ID
邮件标识References
回复邮件标识,逗号隔开In-Reply-To
回复邮件标识(会话发起的第一封)Return-Path
发信任地址(2020/07/31,邮件的 Return-Path 头是什么)Comments
说明Keywords
关键字一般采用 X-
开头的字段名称表示自定义字段,或者叫拓展字段:
常见的拓展字段:
X-Mailer
# cat /etc/logrotate.conf
weekly
su root adm
rotate 4
create
#dateext
#compress
include /etc/logrotate.d
以 Nginx 配置为例:
# cat /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily # 按日切割
missingok # 如果文件不存在,则不创建
rotate 14 # 最多保留 14 个日志文件
compress # 压缩
delaycompress # 延迟压缩
notifempty # 如果文件为空,则不创建
create 0640 www-data adm
# 可能一次切割多个日志,
# 但是后面遇到的每个脚本都只执行一次,
# 在所有日志切割之前或之后
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>&1
endscript
}
其他常用选项:
dateext
部分日志需要添加日期后缀lastaction/endscript
最后执行的指令,很有用,比如最后将日志备份到某些地方比如:
rsync -au *.gz 192.168.64.234:/backup/nginx-logs/`hostname -s`/
从阿帕网 (ARPANET) 时代一直到互联网的早期,网络节点比较少,都是通过本地 hosts 文件来实现主机名到 IP 地址的映射。
根据维基百科的信息,斯坦福研究所负责维护了一个公共 hosts 文件,大家会找他同步 (rfc606, rfc608)。
PS: 这个时候如果有主机名重复了谁来管?打电话过去让他们改名?
这套机制一直运行了十几年,公共 hosts 文件已经变的很大了,变化也很频繁(IP 可能已经不再那么固定了),需要经常同步。这个时候,斯坦福研究所的网络压力也越来越大了。
后来人们开始设计域名和域名相关的公共设施 (rfc805, rfc830)。最后,在 1983 年,形成了下面两个 RFC 文档:
几年后(1987),正式的 DNS 标准 RFC 1034 和 RFC 1035 推出。
这套标准一直运行到现在,可能有对其进行拓展(比如 DNS 记录类型不断添加,Unicode 字符引入),但是基本技术设计没有改变。
https://zhidao.baidu.com/question/1386069665602139980.html
比如本站域名 www.markjour.com, 其完整形式应该是 www.markjour.com.
(后面多一个小数点)
Specification for DNS over Transport Layer Security (TLS)
DNS Queries over HTTPS (DoH)
sudo apt install -y nodejs npm
# 已经不需要这句了:
# sudo ln -s `which nodejs` /usr/bin/node
# node -v
# npm -v
npm config set registry=https://registry.npm.taobao.org
sudo npm upgrade -g npm
# sudo npm install -g yarn --registry=https://registry.npm.taobao.org
curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
yarn config set registry https://registry.npm.taobao.org
echo registry=https://registry.npm.taobao.org > ~/.npmrc
npm config get registry
https://registry.npmjs.org/
npm config set registry=https://registry.npm.taobao.org
yarn config get registry
https://registry.yarnpkg.com
yarn config set registry https://registry.npm.taobao.org
RFC#821 定义的 SMTP 协议非常简单(简陋)。
1993 年,RFC#1425 SMTP Service Extensions 定义了 SMTP 协议的拓展框架。
这个向前兼容的安全拓展框架是通过 EHLO 命令来实现。
注意:这边不是讨论 邮箱地址的格式。
From
发信人To
收信人Cc
抄送人Bcc
密送人Rely-To
回复地址Sender
发信人Return-Path
/ Reverse-Path
/ Envelope-From
碳式复写纸 carbon paper
副本,抄送 carbon copy => CC
密送 blind carbon copy => BCC
按照设计,密送地址不希望被其他收信人、抄送人察觉,只是密送地址才知道自己是密送。
SMTP 服务器不处理 CC、BCC,SMTP 客户端应该自行处理
TO 地址 + CC 地址 + BCC 地址一起放到 SMTP 会话的 RCPT TO 字段
所以,按照我的理解,邮件客户端:
在一次 SMTP 会话中,如果有 3 个 TO/CC 地址,2 个 BCC 地址,应该对那 3 个地址批量发送,然后对那 2 个 BCC 地址分别加上 BCC 头,分别发送。
更稳妥一点:如果是批量发送邮件,不要放 BCC 到邮件头!!!显示一个 密送:xxx
也没啥意义。
from_addr = "from@markjour.com"
to_addrs = ["to@markjour.com"]
cc_addrs = ["cc1@markjour.com", "cc2@markjour.com"]
bcc_addrs = ["bcc@markjour.com"]
msg = f"""
From: {from_addr}
To: {", ".join(to_addrs)}
Cc: {", ".join(cc_addrs)}
Hello World
""".strip()
send_to = to_addrs + cc_addrs + bcc_addrs
server = smtplib.SMTP('smtp.126.com')
server.set_debuglevel(1)
server.login(api_user, api_key)
server.sendmail(from_addr, send_to, msg)
server.quit()
import logging
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
logging.basicConfig(level=logging.DEBUG)
SMTP_HOST = 'smtp.example.com'
SMTP_PORT = 587
SMTP_USERNAME = 'your_username'
SMTP_PASSWORD = 'your_password'
SMTP_STARTTLS = True
sender = 'sender@example.com'
recipients = ['rcpt01@example.com', '中国 <rcpt02@example.com>']
subject = 'Test Email'
content_text = 'This is a test email.'
content_html = '<html><h1>Hello</h1></html>'
def encode_recipient(name, addr):
pass
msg = MIMEMultipart()
msg['From'] = sender
msg['To'] = ', '.join(recipients)
msg['Subject'] = subject
msg.attach(MIMEText(content_text))
msg.attach(MIMEText(content_html))
print(msg.as_string())
def smtp_debug(self, *args):
msg = ' '.join(map(str, args))
logging.debug(msg)
smtp = smtplib.SMTP(SMTP_HOST, SMTP_PORT)
smtp._print_debug = smtp_debug
smtp.set_debuglevel(1)
smtp.ehlo()
if SMTP_STARTTLS:
smtp.starttls()
smtp.ehlo()
if SMTP_USERNAME and SMTP_USERNAME:
smtp.login(SMTP_USERNAME, SMTP_PASSWORD)
smtp.sendmail(sender, recipients, msg.as_string())
smtp.quit()