Fork me on GitHub
Suzf  Blog

Archive Dev

How-to Deploy Django Apps with uWSGI and Nginx on Ubuntu

Django 的部署可以有很多方式,采用 nginx + uwsgi 的方式是其中比较常见的一种方式。

准备工作

安装所需软件

pip install Django
apt-get install nginx

pip install uwsgi
# 注: 在 Debian/Ubuntu 系统中需要安装 python-dev

基本测试

创建一个 test.py 的文件

# test.py
def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"] # python3
    #return ["Hello World"] # python2

运行 uWSGI

uwsgi --http :8000 --wsgi-file test.py

参数含义: http :8000: 使用http协议 8000端口 wsgi-file test.py: 加载指定文件 test.py 执行 Curl 命令在终端上返回 Hello world

$curl http://172.16.9.110:8000
Hello World

如果返回的是正确的内容,说明下面工作流程的组件是工作的:

the web client <-> uWSGI <-> Python

测试你的 Django 项目 确保你的项目是可以顺利运行的

python manage.py runserver 0.0.0.0:8000

如果没有报错,使用 uWSGI 的方式运行它

uwsgi --http :8000 --wsgi-file mysite.wsgi

通过浏览器访问正常, 说明下面工作流程的组件是工作的:

the web client <-> uWSGI <-> Django

配置 Nginx

# mysite_nginx.conf

# the upstream component nginx needs to connect to
upstream django {
    server unix:///path/to/your/mysite/mysite.sock; # for a file socket
    # server 127.0.0.1:8001; # for a web port socket (we'll use this first)
}

# configuration of the server
server {
    # the port your site will be served on
    listen      8000;
    # the domain name it will serve for
    server_name .example.com; # substitute your machine's IP address or FQDN
    charset     utf-8;

    # max upload size
    client_max_body_size 75M;   # adjust to taste

    # Django media
    location /media  {
        alias /path/to/your/mysite/media;  # your Django project's media files - amend as required
    }

    location /static {
        alias /path/to/your/mysite/static; # your Django project's static files - amend as required
    }

    # Finally, send all non-media requests to the Django server.
    location / {
        uwsgi_pass  django;
        include     /path/to/your/mysite/uwsgi_params; # the uwsgi_params file you installed
    }
}

Running the Django application with uswgi and nginx

如果上面一切都显示正常,则下面命令可以拉起django application

uwsgi --socket mysite.sock --wsgi-file mysite.wsgi --chmod-socket=664

Configuring uWSGI to run with a .ini file

每次都运行上面命令拉起django application实在麻烦,使用.ini文件能简化工作,方法如下: 在application目录下创建文件mysite_uwsgi.ini,填入并修改下面内容:

# mysite_uwsgi.ini file
[uwsgi]

# Django-related settings
# the base directory (full path)
chdir           = /path/to/your/project
# Django's wsgi file
module          = project.wsgi
# the virtualenv (full path)
home            = /path/to/virtualenv

# process-related settings
# master
master          = true
# maximum number of worker processes
processes       = 10
# the socket (use the full path to be safe
socket          = /path/to/your/project/mysite.sock
# ... with appropriate permissions - may be needed
# chmod-socket    = 664
# clear environment on exit
vacuum          = true

之后使用这个文件运行 uwsgi

uwsgi --ini mysite_uwsgi.ini # the --ini option is used to specify a file

Once again, test that the Django site works as expected.

参考链接

[0] http://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html

[1] http://www.jianshu.com/p/e6ff4a28ab5a

[译] Python Logging Howto

基本日志记录教程
日志是跟踪一些软件运行时发生的事件的手段。软件的开发人员添加日志调用到他们的代码中,以指示已发生的某些事件。一个事件是通过一个描述性消息可任选地含有可变数据(即是该事件的每次发生潜在不同的数据)中。事件是很重要的,开发者通常通过事件追踪问题, 重要性也可称为水平或严重程度。
什么时候使用 logging
日志提供了简单的日志使用一组方便的功能。这里有 debug()info()warning()error()critical()。要确定何时使用日志记录,请参阅下表,其中规定,对于一组常见任务,使用最好的工具。

Python 简单爬取MM照片

制作爬虫的基本步骤

  1. 需求分析
  2. 分析网站源码<F12>
  3. 编写正则表达式 过滤内容
  4. 生成代码
需求分析
有好多想要的图片,自己又懒得下载;有没有简单而有效地方法呢?

How-to deploy flask web applications use wsgi behind apache

Flask是一个使用Python编写的轻量级Web应用框架。基于Werkzeug WSGI工具箱和Jinja2 模板引擎Flask使用BSD授权。 Flask也被称为“microframework”,因为它使用简单的核心,用extension增加其他功能。Flask没有默认使用的数据库、窗体验证工具。然而,Flask保留了扩增的弹性,可以用Flask-extension加入这些功能:ORM、窗体验证工具、文件上传、各种开放式身份验证技术。 WSGI Web服务器网关接口Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器Web应用程序框架之间的一种简单而通用的接口。自从WSGI被开发出来以后,许多其它语言中也出现了类似接口。

密码散列安全

节选自 PHP manual

相关链接  Wooyun

本部分解释使用散列函数对密码进行安全处理背后的原因, 以及如何有效的进行密码散列处理。

  1. 为什么需要把应用程序中用户的密码进行散列化?
  2. 为何诸如 md5 和 sha1 这样的常见散列函数不适合用在密码保护场景?
  3. 如果不建议使用常用散列函数保护密码, 那么我应该如何对密码进行散列处理?
  4. “盐”是什么?
  5. 我应该如何保存“盐”?

为什么需要把应用程序中用户的密码进行散列化?

当设计一个需要接受用户密码的应用时, 对密码进行散列是最基本的,也是必需的安全考虑。 如果不对密码进行散列处理,那么一旦应用的数据库受到攻击, 那么用户的密码将被窃取。 同时,窃取者也可以使用用户账号和密码去尝试其他的应用, 如果用户没有为每个应用单独设置密码,那么将面临风险。

通过对密码进行散列处理,然后再保存到数据库中, 这样就使得攻击者无法直接获取原始密码, 同时还可以保证你的应用可以对原始密码进行相同的散列处理, 然后比对散列结果。

需要着重提醒的是,密码散列只能保护密码 不会被从数据库中直接窃取, 但是无法保证注入到应用中的 恶意代码拦截到原始密码。

为何诸如 md5()sha1() 这样的常见散列函数不适合用在密码保护场景?

MD5,SHA1 以及 SHA256 这样的散列算法是面向快速、高效 进行散列处理而设计的。随着技术进步和计算机硬件的提升, 破解者可以使用“暴力”方式来寻找散列码 所对应的原始数据。

因为现代化计算机可以快速的“反转”上述散列算法的散列值, 所以很多安全专家都强烈建议 不要在密码散列中使用这些散列算法。

如果不建议使用常用散列函数保护密码, 那么我应该如何对密码进行散列处理?

当进行密码散列处理的时候,有两个必须考虑的因素: 计算量以及“盐”。 散列算法的计算量越大, 暴力破解所需的时间就越长。

PHP 5.5 提供了 一个原生密码散列 API, 它提供一种安全的方式来完成密码 散列验证。 PHP 5.3.7 及后续版本中都提供了一个 » 纯 PHP 的兼容库

PHP 5.3 及后续版本中,还可以使用 crypt() 函数, 它支持多种散列算法。 针对每种受支持的散列算法,PHP 都提供了对应的原生实现, 所以在使用此函数的时候, 你需要保证所选的散列算法是你的系统所能够支持的。

当对密码进行散列处理的时候,建议采用 Blowfish 算法, 这是密码散列 API 的默认算法。 相比 MD5 或者 SHA1,这个算法提供了更高的计算量, 同时还有具有良好的伸缩性。

如果使用 crypt() 函数来进行密码验证, 那么你需要选择一种耗时恒定的字符串比较算法来避免时序攻击。 (译注:就是说,字符串比较所消耗的时间恒定, 不随输入数据的多少变化而变化) PHP 中的 == 和 === 操作符strcmp() 函数都不是耗时恒定的字符串比较, 但是 password_verify() 可以帮你完成这项工作。 我们鼓励你尽可能的使用 原生密码散列 API

“盐”是什么?

加解密领域中的“盐”是指在进行散列处理的过程中 加入的一些数据,用来避免从已计算的散列值表 (被称作“彩虹表”)中 对比输出数据从而获取明文密码的风险。

简单而言,“盐”就是为了提高散列值被破解的难度 而加入的少量数据。 现在有很多在线服务都能够提供 计算后的散列值以及其对应的原始输入的清单, 并且数据量极其庞大。 通过加“盐”就可以避免直接从清单中查找到对应明文的风险。

如果不提供“盐”,password_hash() 函数会随机生成“盐”。 非常简单,行之有效。

我应该如何保存“盐”?

当使用 password_hash() 或者 crypt() 函数时, “盐”会被作为生成的散列值的一部分返回。 你可以直接把完整的返回值存储到数据库中, 因为这个返回值中已经包含了足够的信息, 可以直接用在 password_verify()crypt() 函数来进行密码验证。

下图展示了 crypt()password_hash() 函数返回值的结构。 如你所见,算法的信息以及“盐”都已经包含在返回值中, 在后续的密码验证中将会用到这些信息。

crypt-text-rendered

 

Python time 模块简述

一、简介

time模块提供各种操作时间的函数
说明:一般有两种表示时间的方式:
第一种是时间戳的方式(相对于1970.1.1 00:00:00以秒计算的偏移量),时间戳是惟一的
第二种以数组的形式表示即(struct_time),共有九个元素,分别表示,同一个时间戳的struct_time会因为时区不同而不同
year (four digits, e.g. 1998)
month (1-12)
day (1-31)
hours (0-23)
minutes (0-59)
seconds (0-59)
weekday (0-6, Monday is 0)
Julian day (day in the year, 1-366)
DST (Daylight Savings Time) flag (-1, 0 or 1) 是否是夏令时
If the DST flag is 0, the time is given in the regular time zone;
if it is 1, the time is given in the DST time zone;
if it is -1, mktime() should guess based on the date and time.
夏令时介绍:http://baike.baidu.com/view/100246.htm
UTC介绍:http://wenda.tianya.cn/wenda/thread?tid=283921a9da7c5aef&clk=wttpcts

二、函数介绍
1.asctime()
asctime([tuple]) -> string
将一个struct_time(默认为当时时间),转换成字符串
Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.
When the time tuple is not present, current time as returned by localtime()
is used.

2.clock()
clock() -> floating point number
该函数有两个功能,
在第一次调用的时候,返回的是程序运行的实际时间;
以第二次之后的调用,返回的是自第一次调用后,到这次调用的时间间隔

示例:

[python] view plain copy

import time
if __name__ == '__main__':
time.sleep(1)
print "clock1:%s" % time.clock()
time.sleep(1)
print "clock2:%s" % time.clock()
time.sleep(1)
print "clock3:%s" % time.clock()

输出:
clock1:3.35238137808e-006
clock2:1.00004944763
clock3:2.00012040636
其中第一个clock输出的是程序运行时间
第二、三个clock输出的都是与第一个clock的时间间隔

3.sleep(...)
sleep(seconds)
线程推迟指定的时间运行,经过测试,单位为秒,但是在帮助文档中有以下这样一句话,这关是看不懂
“The argument may be a floating point number for subsecond precision.”

4.ctime(...)
ctime(seconds) -> string
将一个时间戳(默认为当前时间)转换成一个时间字符串
例如:
time.ctime()
输出为:'Sat Mar 28 22:24:24 2009'

5.gmtime(...)
gmtime([seconds]) -> (tm_year, tm_mon, tm_day, tm_hour, tm_min,tm_sec, tm_wday, tm_yday, tm_isdst)
将一个时间戳转换成一个UTC时区(0时区)的struct_time,如果seconds参数未输入,则以当前时间为转换标准

6.localtime(...)
localtime([seconds]) -> (tm_year,tm_mon,tm_day,tm_hour,tm_min,tm_sec,tm_wday,tm_yday,tm_isdst)
将一个时间戳转换成一个当前时区的struct_time,如果seconds参数未输入,则以当前时间为转换标准

7.mktime(...)
mktime(tuple) -> floating point number
将一个以struct_time转换为时间戳

8.strftime(...)
strftime(format[, tuple]) -> string
将指定的struct_time(默认为当前时间),根据指定的格式化字符串输出
python中时间日期格式化符号:
%y 两位数的年份表示(00-99)
%Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(0-31)
%H 24小时制小时数(0-23)
%I 12小时制小时数(01-12)
%M 分钟数(00=59)
%S 秒(00-59)

%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为星期的开始
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身

9.strptime(...)
strptime(string, format) -> struct_time
将时间字符串根据指定的格式化符转换成数组形式的时间
例如:
2009-03-20 11:45:39  对应的格式化字符串为:%Y-%m-%d %H:%M:%S
Sat Mar 28 22:24:24 2009 对应的格式化字符串为:%a %b %d %H:%M:%S %Y

10.time(...)
time() -> floating point number
返回当前时间的时间戳

三、疑点
1.夏令时
在struct_time中,夏令时好像没有用,例如
a = (2009, 6, 28, 23, 8, 34, 5, 87, 1)
b = (2009, 6, 28, 23, 8, 34, 5, 87, 0)
a和b分别表示的是夏令时和标准时间,它们之间转换为时间戳应该相关3600,但是转换后输出都为646585714.0

四、小应用
1.python获取当前时间
time.time() 获取当前时间戳
time.localtime() 当前时间的struct_time形式
time.ctime() 当前时间的字符串形式

2.python格式化字符串
格式化成2009-03-20 11:45:39形式
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())

不加参数时,默认就是输出当前的时间
time.strftime('%Y-%m-%d %H:%M:%S')

格式化成Sat Mar 28 22:24:24 2009形式
time.strftime("%a %b %d %H:%M:%S %Y", time.localtime())

3.将格式字符串转换为时间戳
a = "Sat Mar 28 22:24:24 2009"
b = time.mktime(time.strptime(a,"%a %b %d %H:%M:%S %Y"))

4.将时间字符串转换为时间元组struct_time
time.strptime('2015-09-16 10:27:36','%Y-%m-%d %H:%M:%S')

 

MySQLdb 参数处理的坑

前几天又有同事掉进了给 SQL 的 IN 条件传参的坑,就像 SELECT col1, col2 FROM table1 WHERE id IN (1, 2, 3) 这类 SQL,如果是一个可变的列表作为 IN 的参数,那这个参数应该怎么传呢?

我见过至少这么几种:

id_list = [1, 2, 3]
cursor.execute('SELECT col1, col2 FROM table1 WHERE id IN (%s)', id_list)

这种方式是语法错误的,原因是 MySQLdb 做字符串格式化时占位符和参数个数不匹配。

id_list = [1, 2, 3]
cursor.execute('SELECT col1, col2 FROM table1 WHERE id IN (%s)', (id_list,))

这种方式语法是正确的,但语义是错误的,因为生成的 SQL 是 SELECT col1, col2 FROM table1 WHERE id IN ((‘1’, ‘2’, ‘3’))

id_list = [1, 2, 3]
id_list = ','.join([str(i) for i in id_list])
cursor.execute('SELECT col1, col2 FROM table1 WHERE id IN (%s)', id_list)

这种方式语义也是错误的,因为生成的 SQL 是 SELECT col1, col2 FROM table1 WHERE id IN (‘1,2,3’)

这三种是第一次使用 MySQLdb 给 IN 传参时犯的最多的错误,大多数人遇到第一种错和掉进后两个坑之后,转而采用了下面的方式:

id_list = [1, 2, 3]
id_list = ','.join([str(i) for i in id_list])
cursor.execute('SELECT col1, col2 FROM table1 WHERE id IN (%s)' % id_list)

这个方式对于可信的参数(比如自己生成的列表:range(1, 10, 2))来说可以用,但由于参数未经 escape,对于从用户端接受的不可信参数来说,存在 SQL 注入的风险。

严防 SQL 注入的问题时刻都不能松懈,于是就有了这样的改进版本:

id_list = [1, 2, 3]
id_list = ','.join([str(cursor.connection.literal(i)) for i in id_list])
cursor.execute('SELECT col1, col2 FROM table1 WHERE id IN (%s)' % id_list)

这个方式控制了 SQL 注入问题的滋生,但由于 cursor.connection.literal 是内部接口,并不推荐从外部使用。

然后就有了这样的方式:

id_list = [1, 2, 3]
arg_list = ','.join(['%s'] * len(id_list))
cursor.execute('SELECT col1, col2 FROM table1 WHERE id IN (%s)' % arg_list, id_list)

这个方式是先生成与参数个数相同的 %s 占位,拼出 ‘SELECT col1, col2 FROM table1 WHERE id IN (%s,%s,%s)’ 这样的 SQL,然后使用安全的方式来传参。

就是想传一个参数而已,怎么会这么麻烦呢?触令丧惨!

更正:以下划线内容为未经充分测试的错误结论,仅做记录:

一直以为 MySQLdb 是不支持给 IN 传参的,直到这次又有同事掉坑我才读了 MySQLdb escape 部分的代码,然后发现,MySQLdb 是在很多类型的 Python object 和 SQL 支持的类型之间做自动转换的,比如 MySQLdb 会对 list 和 tuple 内的元素逐个进行 escape,生成一个 tuple,因此这才是正确的给 IN 传参的方式:

id_list = [1, 2, 3]
cursor.execute('SELECT col1, col2 FROM table1 WHERE id IN %s', (id_list,))

可以把 MySQLdb 处理参数的过程简化描述为:

  1. 对参数 (id_list,) 做 escape 得到 ((‘1’, ‘2’, ‘3’),)
  2. 用 escape 过的参数对 SQL 进行格式化:’SELECT col1, col2 FROM table1 WHERE id IN %s’ % ((‘1’, ‘2’, ‘3’),),得到完整 SQL:’SELECT col1, col2 FROM table1 WHERE id IN (‘1’, ‘2’, ‘3’)

整理一下口诀:IN 的参数和其他参数一样,是一个整体,就要不要对属于参数一部分的 () 念念不忘了……

总结一下评论中对这个方法提出的问题:

  1. 如果参数列表只有一个元素,比如 cursor.execute('SELECT col1, col2 FROM table1 WHERE id IN %s', ([1],)),生成的 SQL 是 SELECT col1, col2 FROM table1 WHERE id IN ('1',),是语法错误的
  2. 对列表内元素做 esacpe 时增加的引号会被留下,如果列表元素是字符串,结果会是错误的,比如 cursor.execute('SELECT col1, col2 FROM table1 WHERE id IN %s', (["1", "2"],)) 生成的 SQL 是 SELECT col1, col2 FROM table1 WHERE id IN ("'1'", "'2'"),而对于数字参数恰好能正确工作的原因是,在执行 SQL 时如果列定义是 int 而传参为字符串,MySQL 会做隐式类型转换(Type Conversion in Expression Evaluation)。

MySQLdb 支持对各种类型的 Python object 进行转换和 escape,感兴趣的同学可以看看 MySQLdb.converters_mysql.c*_escape* 系列的函数,另外 MySQLdb 也支持自定义转换规则,参见 MySQLdb.connectconv 参数。

来源:  互联网

 

How to limit Flask dev server to only one visiting ip address

I'm developing a website using the Python Flask framework and I now do some devving, pushing my changes to a remote dev server. I set this remote dev server up to serve the website publically using app.run(host='0.0.0.0').

This works fine, but I just don't want other people to view my website yet. For this reason I somehow want to whitelist my ip so that the dev server only serves the website to my own ip address, giving no response, 404's or some other non-useful response to other ip addresses. I can of course set up the server to use apache or nginx to actually serve the website, but I like the automatic reloading of the website on code changes for devving my website