Python HTTP PythonSimpleServer
2018-05-07
Python2
在当前目录起 HTTP 服务,可以用于测试和临时性的文件下载服务。
# Default bind to 0.0.0.0:8000
python -m SimpleHTTPServer
# Maybe you want to use port 8080
python -m SimpleHTTPServer 8080
Python3
除了可以指定端口,还可以指定绑定地址、工作目录。
# 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
Python Redis
2018-05-06
如果可以的话,使用 redis-cli monitor
命令来输出所有 Redis 命令也很方便。
有时,条件不允许,或者 Redis 需要处理其他的连接,我希望将自己代码调用的 Redis 命令输出到日志中,方便调试。
Email
2018-05-05
相关的文章:
邮件是由纯文本组成,其详细的格式有很多 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
RFC 822 中关于字符的定义
; ( 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-
开头的字段名称表示自定义字段,或者叫拓展字段:
常见的拓展字段:
日志 Linux
2018-05-03
配置
# 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`/
参考资料与拓展阅读
架构 DNS
2018-05-02
历史
从阿帕网 (ARPANET) 时代一直到互联网的早期,网络节点比较少,都是通过本地 hosts 文件来实现主机名到 IP 地址的映射。
根据维基百科的信息,斯坦福研究所负责维护了一个公共 hosts 文件,大家会找他同步 (rfc606, rfc608)。
PS: 这个时候如果有主机名重复了谁来管?打电话过去让他们改名?
这套机制一直运行了十几年,公共 hosts 文件已经变的很大了,变化也很频繁(IP 可能已经不再那么固定了),需要经常同步。这个时候,斯坦福研究所的网络压力也越来越大了。
后来人们开始设计域名和域名相关的公共设施 (rfc805, rfc830)。最后,在 1983 年,形成了下面两个 RFC 文档:
- RFC 882, DOMAIN NAMES - CONCEPTS and FACILITIES
- RFC 883, DOMAIN NAMES - IMPLEMENTATION and SPECIFICATION
几年后(1987),正式的 DNS 标准 RFC 1034 和 RFC 1035 推出。
这套标准一直运行到现在,可能有对其进行拓展(比如 DNS 记录类型不断添加,Unicode 字符引入),但是基本技术设计没有改变。
DNS 的管理权问题
https://zhidao.baidu.com/question/1386069665602139980.html
基本流程
比如本站域名 www.markjour.com, 其完整形式应该是 www.markjour.com.
(后面多一个小数点)
DNS 软件
- BIND
- PowerDNS
- dnsmasq
- Unbound
- CoreDNS
- SmartDNS
Cache-Only DNS Server
新的发展
- 标准的 DNS 是运行在 UDP 53 端口上的。后来的 RFC 1123 增加了 TCP 的支持, 这个方案叫做 DNS over TCP, 还是在 53 端口。
- DNSCrypt, 2011 年设计的, 实现 DNS 的加密和验证,运行于 443 端口。注意:存在于 IETF 框架之外,但是好像有很多服务器支持。
- DNS over TLS (DoT), 2016 年 5 月成为规范。
RFC 7858 Specification for DNS over Transport Layer Security (TLS)
主要作用是加密传输,防止窃听。
- DNS over HTTPS (DoH), 2018 年 10 月成为规范。
RFC 8484 DNS Queries over HTTPS (DoH)
作用和 DoT 一样。
- DNS over TOR, 2019 年。
- Oblivious DNS-over-HTTPS (ODoH), 透过代理的方式,让 DoH 服务器无法获取客户端的真实 IP。同时代理无法获取 DNS 请求的内容。
参考资料与拓展阅读
NodeJS
2018-04-27
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
SMTP Email
2018-04-24
RFC#821 定义的 SMTP 协议非常简单(简陋)。
1993 年,RFC#1425 SMTP Service Extensions 定义了 SMTP 协议的拓展框架。
这个向前兼容的安全拓展框架是通过 EHLO 命令来实现。
Email
2018-04-16
注意:这边不是讨论 邮箱地址的格式。
格式
含义
- SMTP 会话(投递)
- Mail From 真实投递的发信人
- Rcpt To 真实投递的收信人
- 邮件内容(显示)
From
发信人
- 如果和 Mail From 地址不同,可能会显示:由 xxx 代发
To
收信人
Cc
抄送人
Bcc
密送人
Rely-To
回复地址
- 客户端点击回复的时候用的
- 如果没有这个字段,就会回复 From 地址
Sender
发信人
Return-Path
/ Reverse-Path
/ Envelope-From
- 作用是在邮件投递出现问题的时候,邮件服务将邮件退回这个地址
- 如果我们看到这几个名字
- 可能是发信人自己在邮件中声明
- 可能是收信方收到邮件之后添加的,单独字段,或放在 Received 头中
关于抄送和密送
碳式复写纸 carbon paper
副本,抄送 carbon copy => CC
密送 blind carbon copy => BCC
按照设计,密送地址不希望被其他收信人、抄送人察觉,只是密送地址才知道自己是密送。
CC, BCC in SMTP
SMTP 服务器不处理 CC、BCC,SMTP 客户端应该自行处理
TO 地址 + CC 地址 + BCC 地址一起放到 SMTP 会话的 RCPT TO 字段
所以,按照我的理解,邮件客户端:
在一次 SMTP 会话中,如果有 3 个 TO/CC 地址,2 个 BCC 地址,应该对那 3 个地址批量发送,然后对那 2 个 BCC 地址分别加上 BCC 头,分别发送。
更稳妥一点:如果是批量发送邮件,不要放 BCC 到邮件头!!!显示一个 密送:xxx
也没啥意义。
- PS:MSN(Outlook),网易邮箱发出去的邮件,不会加 BCC 头
甚至网易可能在显示邮件原文的时候会移除 BCC 头(给网易邮箱发的 BCC 头都不见了)
- PS:Gmail,QQ 邮箱发出去的邮件,密送人会看到 BCC 头
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()
Python
2018-04-13
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()
Email 时间
2018-04-10
比如:
Sun, 20 Jun 2018 00:47:04 -0700 (PDT)
Thu, 10 Jun 2021 16:10:03 -0700 (PDT)
Thu, 10 Jun 2021 08:06:31 -0700 (PDT)
定义
定义在 RFC 822 的 5. DATE AND TIME SPECIFICATION。
date-time = [ day "," ] date time ; dd mm yy hh:mm:ss zzz
day = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
date = 1*2DIGIT month 2DIGIT ; day month year e.g. 20 Jun 82
month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
time = hour zone ; ANSI and Military
hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT]
; 00:00:00 - 23:59:59
zone = "UT" / "GMT" ; Universal Time
; North American : UT
/ "EST" / "EDT" ; Eastern: - 5/ - 4
/ "CST" / "CDT" ; Central: - 6/ - 5
/ "MST" / "MDT" ; Mountain: - 7/ - 6
/ "PST" / "PDT" ; Pacific: - 8/ - 7
/ 1ALPHA ; Military: Z = UT;
; A:-1; (J not used)
; M:-12; N:+1; Y:+12
/ ( ("+" / "-") 4DIGIT ) ; Local differential
; hours+min. (HHMM)
总结就是:
[day-of-week,] day month year hour:minute[:second] timezone
- 周几和秒是可选的,据我观察,没有邮件省略这两部分
- 周几和月份采用三字母英文缩写(首字母大写)
- 年份是 2 位数字,后来的规范更新中建议采用 4 位数字。出于兼容性考虑,一般都保留了对 RFC 822 两位数字年份的支持。
- 时区除了数字之外,可以使用
UT
、GMT
、EST
、EDT
、CST
、CDT
、MST
、MDT
、PST
、PDT
,
还有 25 个字母(J 没有使用),Z 表示 UTC/GMT 时间,A - M 表示 -1 ~ -12 时区,N - Y 表示 1 到 12 时区。
|
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
West |
A |
B |
C |
D |
E |
F |
G |
H |
I |
K |
L |
M |
Eest |
N |
O |
P |
Q |
R |
S |
T |
U |
V |
W |
X |
Y |
Python
生成符合要求的时间字符串比较简单:
import time
time.strftime('%a, %d %b %Y %H:%M:%S %z')
# 'Tue, 10 Apr 2018 09:10:05 +0800'
但是由于这个灵活度比较大,解析起来最好借助专业的库(email.utils
)来做这个事。
import time
import datetime
import email.utils
import pytz
# 解析 ############################################
date_str = 'Sun, 20 Jun 2018 00:47:04 -0700 (PDT)'
email.utils.parsedate_to_datetime(date_str)
# datetime.datetime(2018, 6, 20, 0, 47, 4, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200)))
email.utils.parsedate_tz(date_str)
(2018, 6, 20, 0, 47, 4, 0, 1, -1, -25200)
# 生成 ############################################
# email.utils.formatdate(timeval=None, localtime=False, usegmt=False)
email.utils.formatdate()
# 'Tue, 10 Apr 2018 09:10:41 -0000'
# email.utils.format_datetime(dt, usegmt=False)
dt = datetime.datetime.now()
email.utils.format_datetime(dt)
# 'Tue, 10 Apr 2018 09:16:43 -0000'
tz = pytz.timezone('Asia/Shanghai') # <DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>
dt = datetime.datetime(2018, 4, 10, 9, 10, 0, tzinfo=tz)
# datetime.datetime(2018, 4, 10, 9, 10, tzinfo=<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>)
email.utils.format_datetime(dt)
# 'Tue, 10 Apr 2018 09:10:00 +0806'