#259 Python 打包上传到 PyPI

2018-05-26

首先你得有 PyPI 的账号,没有注册的话,不用搞了。
然后你肯定要先准备 setup.py 了,如果这个都没弄就不用搞了。

算了,先贴一个简单的样板吧。

setup.py

# -*- coding: utf-8 -*-

from setuptools import setup

setup(
    name='PageageName',
    version='0.0.1',
    author='YourName',
    author_email='YourEmail',
    url='PackageSite (e.g. GitHub)',
    description='....',
    long_description=open('README.md').read(),
    long_description_content_type='text/markdown',
    packages=['PackageDir'],
    install_requires=[
        'requests>=2.18.4',
        'balabala',
    ],
    extras_require={  # 可选
        'dev': [
            'balabala...',
        ],
        'test': [
            'balabala...',
        ],
    },
    classifiers=[
        'Development Status :: 3 - Alpha',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'License :: OSI Approved :: MIT License',
    ],
)

流程

python3 -m pip install twine

rm -rf dist

python3 setup.py sdist bdist_wheel
python2 setup.py sdist bdist_wheel

twine upload dist/*

twine 会询问账号密码,如果不想每次这么麻烦,可以创建 ~/.pypirc 文件。

.pypirc 文件

[distutils]
index-servers =
    pypi

[pypi]
repository = https://upload.pypi.org/legacy/
username = your_username
password = your_password

参考资料

#258 MIME 编码

2018-05-19

说明

MIME 是 Multipurpose Internet Mail Extensions 的缩写,多用途互联网邮件扩展 的意思。

最初的电子邮件标准 RFC 822 只支持发送 ASCII 字符文本内容,通过 MIME 这个拓展(RFC 2822),可以发送所有类型的内容。
后面的 HTTP 协议也是在 MIME 这个框架内构建的,Web 开发者比较熟悉的那几个 Content-XXX 头就是从 MIME 里面来的。

规范文件

RFC No. Type Title
RFC 2045 Standards Track Multipurpose Internet Mail Extensions (MIME) Part One:
Format of Internet Message Bodies
RFC 2046 Standards Track Multipurpose Internet Mail Extensions (MIME) Part Two:
Media Types
RFC 2047 Standards Track MIME (Multipurpose Internet Mail Extensions) Part Three:
Message Header Extensions for Non-ASCII Text
RFC 2048 Best Current Practice Multipurpose Internet Mail Extensions (MIME) Part Four:
Registration Procedures
RFC 2049 Standards Track Multipurpose Internet Mail Extensions (MIME) Part Five:
Conformance Criteria and Examples

语法

5 个核心头(RFC 2045)和 Content-Disposition 扩展(RFC 2183):

MIME-Version: 1.0                           // MIME 版本
Content-Type: [type]/[subtype]; parameter   // 内容类型
Content-Transfer-Encoding: [encoding]       // 内容传输编码
Content-ID: xxx
Content-Description: xxx                    // some descriptive information
Content-Disposition: [disposition]          // 内容配置

内容类型 Content-Type

又叫互联网媒体类型(Internet media type)或者 MIME 类型(MIME type)。
类型信息的注册事宜,由 IANA(Internet Assigned Numbers Authority)统一管理。

按照注册来源分成几种类型,只用关心标准数,这个了解一下就行了:

  • 标准树 类型名 / 子类型名 [ + 后缀 ] [ ; 可选参数 ]
  • 厂商树 类型名 / vnd.子类型名 [ + 后缀 ] [ ; 可选参数 ],例如:application/vnd.debian.binary-package
  • 个人树 类型名 / prs.子类型名 [ + 后缀 ] [ ; 可选参数 ]
  • 未注册的 x.树 类型名 / x.子类型名 [ + 后缀 ] [ ; 可选参数 ]

常见的类型

详细类型在 IANA 官网有,链接在下面参考资料部分我贴了一个。

Type Subtype Description
text plain 文本
text html HTML
text xml XML
text javascript JavaScript
text css CSS
text csv CSV
text vcard vCard 电子名片
image bmp
image jpeg
image png
image gif
image webp
image svg+xml
image icon
audio mpeg
audio mp4
audio ogg
audio webm
audio flac
video mpeg
video mp4
video ogg
video webm
application xml XML
application json JSON
application ecmascript
application javascript
application zip
application gzip
application pdf
application rss+xml
application atom+xml
application octet-stream

内容传输编码

编码类型主要是这几种:“7bit”,“8bit”,“binary”,“quoted-printable”,“base64”。

#257 SMTP 认证

2018-05-18

最常见的三种 SMTP 认证方法:

  • PLAIN
  • LOGIN
  • CRAM-MD5

#255 Python 应用: 简易 SMTP 服务器

2018-05-07

Python2

python2 -m smtpd -h
# An RFC 2821 smtp proxy.
# Usage: /usr/lib/python2.7/smtpd.py [options] [localhost:localport [remotehost:remoteport]]
#     --nosetuid, -n
#     --version, -V
#     --class classname, -c classname
#     --debug, -d
#     --help, -h

Python3

python -m smtpd -h
# An RFC 5321 smtp proxy with optional RFC 1870 and RFC 6531 extensions.
# Usage: /usr/lib/python3.9/smtpd.py [options] [localhost:localport [remotehost:remoteport]]
#     --nosetuid, -n  默认会设置用户为 nobody,如果不是 root 会因权限不足失败
#     --version, -V
#     --class classname, -c classname 默认: PureProxy
#     --size limit, -s limit 消息大小限制(RFC 1870 SIZE extension),默认是 33554432 字节,即 32MB
#     --smtputf8, -u 启用 SMTPUTF8 扩展(RFC 6531)
#     --debug, -d
#     --help, -h

# 如果不指定主机,就使用 localhost
# 如果主机是 localhost,端口使用 8025
# 如果是其他主机,端口使用 25
python3 -m smtpd -n

# 默认的 PureProxy 会给转信出去,正常情况会被服务器拒绝
python3 -m smtpd -n -c smtpd.DebuggingServer

Python 3.9 的 PureProxy 有 BUG,会报 process_message() got an unexpected keyword argument 'mail_options'

自定义黑洞服务器

blackhole.py

import smtpd
import time
class BlackHoleServer(smtpd.SMTPServer):
    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        print('%s %s %s -> %s' % (time.strftime('%Y-%m-%d %H:%M:%S'), peer, mailfrom, rcpttos))

setup.py

import setuptools
setuptools.setup(name="blackhole", py_modules=["blackhole"])

附件下载:blackhole.zip

python setup.py install --user
python -m smtpd -n -c blackhole.BlackHoleServer

测试

import smtplib
smtp = smtplib.SMTP('localhost', 8025)
from_addr = 'admin@markjour.com'
to_addr = 'you@markjour.com'
smtp.sendmail(from_addr, to_addr,
    f"""From: {from_addr}\nTo: {to_addr}\nSubject: just4fun\n\nhello, world!""")

#253 Python 应用: 简易 HTTP 服务器

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

#252 Python Redis Debug

2018-05-06

如果可以的话,使用 redis-cli monitor 命令来输出所有 Redis 命令也很方便。

有时,条件不允许,或者 Redis 需要处理其他的连接,我希望将自己代码调用的 Redis 命令输出到日志中,方便调试。

#251 邮件格式 (RFC 822)

2018-05-05

相关的文章:

邮件是由纯文本组成,其详细的格式有很多 RFC 规范需要遵守。我这里只能对我所了解的,也是基础的 —— 或者说最核心的 —— 格式做一个说明。

最核心的部分是 1982 年的 RFC 822 (STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES),之后又做过一些更新, 比如 RFC 2822RFC 5322 (Internet Message Format) 和一堆补丁更新。本文要讲的基本格式,从开始到现在并没有什么明显变化。

  1. 邮件是一种纯文本格式,最开始只包含 ASCII 字符,后来引入了 MIME 之后,可以制定别的编码,比如 UTF-8 等。

  2. 换行符是 \r\n,也就是 CR + LF

  3. 整体来说,一封邮件由邮件头(Headers)和邮件体(Payload)组成。

  4. 邮件头包含若干个头字段

  5. 邮件头和邮件体之间用一个空行隔开

  6. 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- 开头的字段名称表示自定义字段,或者叫拓展字段:

常见的拓展字段:

  • X-Mailer

#250 Logrotate

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`/

参考资料与拓展阅读