假定已经安装了nginx, python2.7, pip, virtualenv

安装uwsgi

pip install uwsgi
ln -s /usr/local/python2.7/bin/uwsgi /usr/bin/

配置nginx的虚拟主机

server
{
    listen 80;
    server_name <SERVER_NAME>;
    root <HOME_DIR>;

    location / {
        try_files $uri @uwsgi;
    }

    location @uwsgi {
        include uwsgi_params;
        uwsgi_pass 127.0.0.1:89;
        uwsgi_param UWSGI_SCRIPT <SCRIPT_NAME>;
        uwsgi_param UWSGI_PYHOME <HOME_DIR>/venv;
        uwsgi_param UWSGI_CHDIR <HOME_DIR>;
    }
    location ~ .*\.(py|pyc|cgi)?$ {
        return 404;
    }
}

SCRIPT_NAME是可执行wsgi的程序名称,并且不带 .py

修改程序,使兼容uwsgi接口
以web.py为例

import web
app = web.application(urls, globals())

if __name__ == "__main__":
    app.run()
else:
    application = app.wsgifunc()

启动uwsgi程序

uwsgi -s :89 -H venv/ -w controller

观察是否有错误,若有错误查看日志进行改正

改为开机自启动

vi /etc/init.d/uwsgi
#! /bin/sh
# chkconfig: 2345 55 25
# Description: Startup script for uwsgi webserver on Debian. Place in /etc/init.d and
# run 'update-rc.d -f uwsgi defaults', or use the appropriate command on your
# distro. For CentOS/Redhat run: 'chkconfig --add uwsgi'

### BEGIN INIT INFO
# Provides:          uwsgi
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the uwsgi web server
# Description:       starts uwsgi using start-stop-daemon
### END INIT INFO

# Author:   licess
# website:  http://lnmp.org

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="uwsgi daemon"
NAME=uwsgi
DAEMON=/usr/local/python2.7/bin/uwsgi
CONFIGFILE=/etc/$NAME.ini
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

set -e
[ -x "$DAEMON" ] || exit 0

do_start() {
    $DAEMON $CONFIGFILE || echo -n "uwsgi already running"
}

do_stop() {
    kill -9 `cat $PIDFILE` || echo -n "uwsgi not running"
    rm -f $PIDFILE
    echo "$DAEMON STOPED."
}

do_reload() {
    $DAEMON --reload $PIDFILE || echo -n "uwsgi can't reload"
}

do_status() {
    ps aux|grep $DAEMON
}

case "$1" in
 status)
    echo -en "Status $NAME: \n"
    do_status
 ;;
 start)
    echo -en "Starting $NAME: \n"
    do_start
 ;;
 stop)
    echo -en "Stopping $NAME: \n"
    do_stop
 ;;
 reload|graceful)
    echo -en "Reloading $NAME: \n"
    do_reload
 ;;
 *)
    echo "Usage: $SCRIPTNAME {start|stop|reload}" >&2
    exit 3
 ;;
esac

exit 0

chmod +x /etc/init.d/uwsgi

chkconfig --add uwsgi
chkconfig uwsgi on

建立uwsgi的配置文件

vi /etc/uwsgi.ini
[uwsgi]
socket = 127.0.0.1:89
master = true
vhost = true
no-stie = true
#workers = 2
#reload-mercy = 10
#vacuum = true
#max-requests = 1000
#limit-as = 512
#buffer-size = 30000
pidfile = /var/run/uwsgi.pid
daemonize = /home/wwwlogs/uwsgi/uwsgi.log
disable-logging = true

发现一个很全的文档,赞一个
http://www.cnblogs.com/xiongpq/p/3381069.html
http://www.cnblogs.com/zhouej/archive/2012/03/25/2379646.html

发现部署python网站需要依赖太多知识了。
nginx python uwsgi virtualenv 等等
所以总的来说部署python网站很难,向那些曾经奉献给部署python网站的人致敬。

好久没有写文章了,因为最近在搞python+flask,本想先发一篇flask方面的文章,但是这篇博客却更早面世了。这篇博客也是在认识他之后发的第一篇博客。
因为他要做一个项目,需要用python爬虫抓取页面,经分析之后存进数据库,因为抓取的数据有些字段不全,所以用关系型数据库比较浪费空间。
于是本着厉行节约的原则,采用比较松散化的非关系型数据库来存储数据(出发点是对的,但是殊不知非关系型数据库更浪费磁盘空间),不管怎么样,还是选择了用非关系型数据库,其实也没别的意思,只是觉得有新技术不用也是一种浪费。
于是准备采用python+mongodb来完成这个项目。

安装mongodb:这方面资料很多了,用apt-get install mongodb 也行,自己编译也行,这里不是重点。
mongodb服务器端安装好了,输入mongo命令进入交互式命令行界面

cf@ubuntu:~$ mongo
MongoDB shell version: 2.0.4
connecting to: test
> 

查看内置帮助

> help
	db.help()                    help on db methods
	db.mycoll.help()             help on collection methods
	rs.help()                    help on replica set methods
	help admin                   administrative help
	help connect                 connecting to a db help
	help keys                    key shortcuts
	help misc                    misc things to know
	help mr                      mapreduce

	show dbs                     show database names
	show collections             show collections in current database
	show users                   show users in current database
	show profile                 show most recent system.profile entries with time >= 1ms
	show logs                    show the accessible logger names
	show log [name]              prints out the last segment of log in memory, 'global' is default
	use <db_name>                set current database
	db.foo.find()                list objects in collection foo
	db.foo.find( { a : 1 } )     list objects in foo where a == 1
	it                           result of the last line evaluated; use to further iterate
	DBQuery.shellBatchSize = x   set default number of items to display on shell
	exit                         quit the mongo shell
> 

mongodb中最大的数据体是服务器(host),在一个服务器上可以有若干个数据库(db),一个数据库中有若干个数据集合(collection),一个数据集合中有若干条文档。与关系型数据库类似,只不过关系型数据库中的数据表在这里变成了数据集,记录变成了文档。

show dbs 显示数据库名字
show collections 显示数据集合名字
use foo 使用foo数据库

> db.help()
DB methods:
	db.addUser(username, password[, readOnly=false])
	db.auth(username, password)
	db.cloneDatabase(fromhost)
	db.commandHelp(name) returns the help for the command
	db.copyDatabase(fromdb, todb, fromhost)
	db.createCollection(name, { size : ..., capped : ..., max : ... } )
	db.currentOp() displays the current operation in the db
	db.dropDatabase()
	db.eval(func, args) run code server-side
	db.getCollection(cname) same as db['cname'] or db.cname
	db.getCollectionNames()
	db.getLastError() - just returns the err msg string
	db.getLastErrorObj() - return full status object
	db.getMongo() get the server connection object
	db.getMongo().setSlaveOk() allow this connection to read from the nonmaster member of a replica pair
	db.getName()
	db.getPrevError()
	db.getProfilingLevel() - deprecated
	db.getProfilingStatus() - returns if profiling is on and slow threshold 
	db.getReplicationInfo()
	db.getSiblingDB(name) get the db at the same server as this one
	db.isMaster() check replica primary status
	db.killOp(opid) kills the current operation in the db
	db.listCommands() lists all the db commands
	db.logout()
	db.printCollectionStats()
	db.printReplicationInfo()
	db.printSlaveReplicationInfo()
	db.printShardingStatus()
	db.removeUser(username)
	db.repairDatabase()
	db.resetError()
	db.runCommand(cmdObj) run a database command.  if cmdObj is a string, turns it into { cmdObj : 1 }
	db.serverStatus()
	db.setProfilingLevel(level,<slowms>) 0=off 1=slow 2=all
	db.shutdownServer()
	db.stats()
	db.version() current version of the server
	db.getMongo().setSlaveOk() allow queries on a replication slave server
	db.fsyncLock() flush data to disk and lock server for backups
	db.fsyncUnock() unlocks server following a db.fsyncLock()
> 

这些不经常用。。。
db.getCollectionNames() 返回数据集合名称列表

> db.test.help()
DBCollection help
	db.test.find().help() - show DBCursor help
	db.test.count()
	db.test.dataSize()
	db.test.distinct( key ) - eg. db.test.distinct( 'x' )
	db.test.drop() drop the collection
	db.test.dropIndex(name)
	db.test.dropIndexes()
	db.test.ensureIndex(keypattern[,options]) - options is an object with these possible fields: name, unique, dropDups
	db.test.reIndex()
	db.test.find([query],[fields]) - query is an optional query filter. fields is optional set of fields to return.
	                                              e.g. db.test.find( {x:77} , {name:1, x:1} )
	db.test.find(...).count()
	db.test.find(...).limit(n)
	db.test.find(...).skip(n)
	db.test.find(...).sort(...)
	db.test.findOne([query])
	db.test.findAndModify( { update : ... , remove : bool [, query: {}, sort: {}, 'new': false] } )
	db.test.getDB() get DB object associated with collection
	db.test.getIndexes()
	db.test.group( { key : ..., initial: ..., reduce : ...[, cond: ...] } )
	db.test.mapReduce( mapFunction , reduceFunction , <optional params> )
	db.test.remove(query)
	db.test.renameCollection( newName , <dropTarget> ) renames the collection.
	db.test.runCommand( name , <options> ) runs a db command with the given name where the first param is the collection name
	db.test.save(obj)
	db.test.stats()
	db.test.storageSize() - includes free space allocated to this collection
	db.test.totalIndexSize() - size in bytes of all the indexes
	db.test.totalSize() - storage allocated for all data and indexes
	db.test.update(query, object[, upsert_bool, multi_bool])
	db.test.validate( <full> ) - SLOW
	db.test.getShardVersion() - only for use with sharding
	db.test.getShardDistribution() - prints statistics about data distribution in the cluster
> 

db.test.ensureIndex()建立索引,唯一键等
db.test.insert() 插入记录
save函数实际就是根据参数条件,调用了insert或update函数.如果想插入的数据对象存在,insert函数会报错,而save函数是改变原来的对象;如果想插入的对象不存在,那么它们执行相同的插入操作.这里可以用几个字来概括它们两的区别,即所谓”有则改之,无则加之”.
db.test.find() 查询

> db.test.find().help()
find() modifiers
	.sort( {...} )
	.limit( n )
	.skip( n )
	.count() - total # of objects matching query, ignores skip,limit
	.size() - total # of objects cursor would return, honors skip,limit
	.explain([verbose])
	.hint(...)
	.showDiskLoc() - adds a $diskLoc field to each returned object

Cursor methods
	.forEach( func )
	.map( func )
	.hasNext()
	.next()
> 

与关系型数据库方法类似。

以上就是几个简单的命令。
mongodb的数据库,数据集可以隐式创建,直接用db.database_name.collection_name即可访问数据集合。

接下来要安装接口,因为我经常使用的是php和python语言,所以要装这两个语言的扩展模块

php扩展模块的安装参见 http://www.php.net/manual/en/mongo.installation.php
python扩展模块pymongo的安装使用命令即可 easy_install pymongo

php下访问mongodb:

<?php

// 链接服务器
$m = new MongoClient();

// 选择一个数据库
$db = $m->database_name;

// 选择一个集合
$collection = $db->collection_name;

// 插入一个文档
$document = array( "title" => "Calvin and Hobbes", "author" => "Bill Watterson" );
$collection->insert($document);

// 添加另一个文档,它的结构与之前的不同
$document = array( "title" => "XKCD", "online" => true );
$collection->insert($document);

// 查询集合中的所有文档
$cursor = $collection->find();

// 遍历查询结果
foreach ($cursor as $document) {
    echo $document["title"] . "\n";
}

?>

python访问mongodb:

>>> import pymongo
>>> client = pymongo.MongoClient("localhost", 27017)
>>> db = client.test
>>> db.name
u'test'
>>> db.my_collection
Collection(Database(MongoClient('localhost', 27017), u'test'), u'my_collection')
>>> db.my_collection.save({"x": 10})
ObjectId('4aba15ebe23f6b53b0000000')
>>> db.my_collection.save({"x": 8})
ObjectId('4aba160ee23f6b543e000000')
>>> db.my_collection.save({"x": 11})
ObjectId('4aba160ee23f6b543e000002')
>>> db.my_collection.find_one()
{u'x': 10, u'_id': ObjectId('4aba15ebe23f6b53b0000000')}
>>> for item in db.my_collection.find():
...     print item["x"]
...
10
8
11
>>> db.my_collection.create_index("x")
u'x_1'
>>> for item in db.my_collection.find().sort("x", pymongo.ASCENDING):
...     print item["x"]
...
8
10
11
>>> [item["x"] for item in db.my_collection.find().limit(2).skip(1)]
[8, 11]

curses是在命令行模式下画图的一种工具,在旧式的gui中使用较多
curses可以在命令行里显示进度条,用python实现:

import curses,time
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
oldsize = (0,0)

for i in range (100):
    scrsize = stdscr.getmaxyx()
    if oldsize != scrsize:
        stdscr.clear()
    oldsize = scrsize
    stdscr.addstr(12, (scrsize[1]-20)/2, "Window size:"+str(scrsize))
    stdscr.addstr(3, 0, '.'*((scrsize[1]-5)*i/100)+str(i)+'%')
    stdscr.refresh()
    time.sleep(0.1)

curses.nocbreak()
curses.echo()
curses.endwin()

curses还有一些优秀的特性,由于时间有限,就不多折腾了。

前几天从师兄那里得到了一块笔记本屏幕,希望利用此屏幕组装成一个显示器。
首先,从淘宝上购买液晶屏套件,一个主控板,一个电源,一个屏幕连接线,一个高压板,一对喇叭,一个遥控器,等。

没有购买按键板,因为可以自己定做,但是买回来的时候,我发现,按键板的接口是2.00mm的,手头没有这样的接插件,所以,用2.00mm的双排母进行改装,将双排母剪短,然后把双排母减去一半,变为单排母,焊上线即可。因为2.00mm的双排母比较多,懒得去买合适的接插件,只好这样了。

按键板上还有一个指示灯,是一个双色的指示灯,手头有这样的指示灯,但手头的是共阳极的LED,显示器上的是共阴极的LED,而且LED还不带电阻,还要自己传一个电阻,以免烧坏LED。

接下来是刷程序了,老板给了一个网址,从网址上就可以下载程序了 http://dl.vmall.com/c0ahog1hgl 密码是123123
里面有详细的说明文档。
找到自己显示器的型号,其实不用具体型号,只要分辨率,电压,连接线模式对应就行了,这些都可以通过屏幕的型号来查到。
我手中这块屏分辨率是1280×800的,是一块旧屏,电源3.3v,模式是单6(S6),找到对应的程序,将bin文件拷入U盘的根目录。
在关机状态插入U盘,按住任意键,插入电源,此时指示灯闪动,屏幕上也显示“正在烧写程序,请勿断电”,大约半分钟,屏幕变黑,刷机完毕。

按下开机键(K0)开机,使用输入选择按钮,选择输入源,这款板子有4个输入源,电视 HDMI 视频 电脑,常用的就是HDMI和电脑了。
接下来测试VGA的输入,将显示器的输入源选择为电脑,然后连接只电脑的VGA插口,电脑自动识别,调整分辨率后,显示效果很好,无闪烁。
接下来测试HDMI的输入,但是无论是来自于数码相机还是树莓派,或者笔记本电脑的输入,对伊显示器来说,都是不稳定的,带有纹波的,而且过扫描的。
这个问题应该不在少数,无论是那个分辨率,都会把周围部分的一圈显示在显示器以外,想起家里电视的HDMI输入也有这样的情况,而高端显示器是没有这种情况的,而且在树莓派上,专门有这样的设置,可以增加或者减小黑边的宽度。
所以下结论,应该是显示器主控板程序的问题。

所以有些失望,本来买这个主控板就是为了他的HDMI输出功能,但事实证明这个功能是鸡肋,可以显示,但问题多多。所以总的来说是失望的。

宝贝链接

http://trade.taobao.com/trade/detail/tradeSnap.htm?spm=a1z09.2.9.61.8R0CFy&tradeID=596867045343760

IMG_1481

IMG_1480

很久之前,我知道有svn这个东西,还挺不错。
后来使用svn作为团队的代码管理工具。
于是遇到了个问题,代码仓库中的代码怎么能够和服务器的代码同步呢?

=========================
这个问题纠结了很久,之前用apache+cgi使用网页点击手动更新,但是不成功,应该(现在肯定了)是权限问题。
apache的cgi是以www用户执行的,没有权限使用svn。我知道crontab里的命令是以root执行的,所以在crontab里每一分钟更新一次。

后来知道了钩子hook,这个东西,可以让每次版本库有新版本时,服务器会自动更新副本。post_comit钩子指向一个程序,由程序运行更新指令,钩子程序是以root执行的,所以没有权限的问题了。

import os,time,commands,stat

auto_up = ['111','222','333']

base_dir = '/home/wwwroot/'
dir = '/home/wwwroot/admin/svntool'
svn_user = 'xxx'
svn_pass = 'xxx'
owner_id = 501
owner_group = 501
svn_up = '/usr/bin/svn up --username=' + svn_user + ' --password=' + svn_pass + ' --no-auth-cache --non-interactive ';
svn_look = 'svnlook changed /home/wwwbase/svnrepos/'

print time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
# auto update files
print 'Auto update dirs:'
for p in auto_up:
	print "\t", p
	commands.getstatusoutput(svn_up + base_dir + p)

# change owners
print 'Change owners:'
(rc1,rs1) = commands.getstatusoutput(svn_look)
up_list = rs1.split("\n")
for f in up_list:
	file_name = f[4:]
	if f[0] != 'D':
		if os.path.exists(base_dir+file_name):
			os.chown(base_dir+file_name, owner_id, owner_group)
			if f[-3:] == '.py' or f[-3:] == '.sh' or f[-3:] == '.pl':
				os.chmod(base_dir+file_name, stat.S_IREAD|stat.S_IWRITE|stat.S_IXUSR|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH)
			print "\t", f[:3], file_name
print

——————————-
这个脚本可以自动更新111,222,333,这几个子项目,并且改变属主,如果是可执行文件,会增加执行权限。

看似不错了,但是有时候不想自动更新,而想通过网页手动更新,这个问题还是没有解决的。
分析原因,还是因为权限,执行更新操作的程序必须要有root权限。

引用一个中间程序,可以获取cgi传过来的参数,然后根据参数选择性的更新,这样就行了。中间程序类似一个守护程序,一直运行在后台。
那cgi与中间程序如何通信,可以通过磁盘文件,消息通信,socket通信。

最后选定使用socket通信来解决问题。

程序有两个部分,一个是cgi程序,用来读取get的参数,向套接字发送消息,并将返回的内容打印出来。
另一个是守护程序,用来监听一个socket,当socket有数据时,执行svn更新动作。

client代码

#!/usr/bin/python
print
import cgi,socket
form = cgi.FieldStorage()
dir = form.getvalue('dir')
if not dir: dir="admin"
base_dir = '/home/wwwroot/'
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect("/tmp/svnupdate.sock")
sock.send(base_dir + dir)
print base_dir + dir
print sock.recv(1024)
sock.close()

server 代码

import socket,os,commands,stat
sockfile="/tmp/svnupdate.sock"
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
if os.path.exists(sockfile):
    os.unlink(sockfile)
sock.bind(sockfile)
sock.listen(5)
os.chmod(sockfile,stat.S_IRWXU|stat.S_IRWXG|stat.S_IRWXO)
print "Listening"
while True:
    connection,address = sock.accept()
    dir = connection.recv(1024)
    print 'svn up ' + dir
    (rc,rs) = commands.getstatusoutput('svn up ' + dir)
    connection.send(rs)
connection.close()

===============================
访问http://xxxxx/svntool/svn_up_client.py?dir=xx 即可更新svn

后记
python守护进程

http://lihuipeng.blog.51cto.com/3064864/1047085

以守护进程开机运行svn_up_server.py

写一个index.py的入口文件
提供多个锚链接

#!/usr/bin/python
print "Content-type: text/html"
print
lst=['111', '222', '333']
for i in lst:
    print "<a href='svn_up_client.py?dir=%s'>svn up /wwwroot/%s</a><br />"%(i,i)

这种cgi程序对换行符也有严格要求,要用\n,而不是\r\n。

python很强大,在抓包领域,配合使用python_libpcap库,可以做到方便抓包处理包的功能。

可惜的是python默认没有安装依赖的库,要手动安装。需要安装pcap,dpkt两个依赖包
网上有两个版本的pcap,一个是
apt-get install libpcap-dev
pylibpcap-0.6.2.tar.gz
python setup.py install
另一个是 apt-get install python-libpcap

两个库好像有些不同,一第一个为准吧。
装好第一个之后,在目录里面会找到一个example的文件夹,里面有两个测试程序,sniffer.py这个程序给了我很大的参考空间。
使用方法是usage: sniff.py <interface> <expr>
第一个参数是网卡名称,一般是eth0 第二个参数是BPF过滤器。
一个网卡会有大量的数据包,如果不用过滤器,直接查看的话,真是一场灾难,BPF过滤器就是用来过滤这些数据包的。但是BPF的资料比较少。
常用的BPF有 tcp ip udp src/dst port 80 host baidu.com ether proto 0x888e 等等
参考 http://www.cnpaf.net/Class/winpcap/200810/23011.html
我们的目标是抓到目的地址是renren.com的包,然后把cookie提取出来。所以过滤器是 “dst host www.renren.com”,抓到包之后,不是每一个包都有cookie信息,简单起见,认为cookie信息只存在一个包内。
接下来是分析包的内容了,包从外到内有3层封装,最外一层是以太网包,中间是IP包,再里面是tcp包,最里面是http报文。
以太网报文:
6字节 目标MAC地址
6字节 原始MAC地址
2字节 类型
46-1500字节 数据
4字节 FCS
IP报文:
4bit版本 4bit首部长度
1字节 服务类型
2字节 长度
2字节 认证
2字节 标志,段偏移量
1字节 TTL
1字节 协议
2字节 校验和
4字节 源IP地址
4字节 目的IP地址
(可选) 其他选项
TCP报文:
2字节 源端口号
2字节 目的端口号
4字节 序列号
4字节 确认序列号
4bit 首部长度
6bit 保留
6bit 标志位
2字节 窗口大小
2字节 校验和
2字节 紧急指针
(可选) 其他选项

HTTP报文:

GET|POST|.. URL HTTP/1.1
[Option: Value]

示例程序中有以太网报文解包的代码,可以直接重用,至于IP,TCP和HTTP的解包过程,其实可以不做,只要在以太网包里面用正则匹配出Cookie的值就可以了。至于不解包就直接正则匹配,结果完全不受影响。

 

#! /usr/bin/env python
import pcap
import re

def print_packet(pktlen, data, timestamp):
    if not data:
        return

    m=re.search(r"Cookie(.+)",data)
    if m:
        print m.group(0)

if __name__=='__main__':
    p = pcap.pcapObject()
    p.open_live("eth0", 1600, 0, 100)
    p.setfilter("dst host www.renren.com", 0, 0)

    try:
        while 1:
            p.dispatch(1, print_packet)
    except KeyboardInterrupt:
        print 'shutting down'
        print '%d packets received, %d packets dropped, %d packets dropped by interface' % p.stats()

运行程序,输出的就是通过网关访问renren.com留下的cookie,使用这个cookie可以模拟用户登录,当然,这仅仅是个实验,不要用在非法的用途。

一些地方还需要改进,当url或者cookie过长,cookie会落在两个包里,这样的话,cookie是不完整的。

可以将正则表达式改成 “Cookie(.+)(\s+)” 可以避免抓到不完整的Cookie。

 

nginx是个小巧灵活的服务器,因此,不免缺少一些功能,在配置过程中带来不便,因此需要安装几个常用的第三方模块

HttpSetMiscModule

http://wiki.nginx.org/HttpSetMiscModule

https://github.com/agentzh/set-misc-nginx-module/archive/v0.24.tar.gz

其中常用的指令是set_escape_uri 可以转义和反转义url,在重定向并且带返回url的时候常用到,比如弹出验证码,当验证码填写正确后,调回原有页面,那么需要一下配置。

if ( -f /usr/local/nginx/conf/catchbot/blacklist/$remote_addr )
{
set_escape_uri $myuri $request_uri;
rewrite “^((?!/captcha/).)*$” /captcha/?captcha_uri= $myuri redirect;
}

HttpSubModule

http://wiki.nginx.org/HttpSubModule

内置,不需下载,默认不编译

其中常用指令是 sub_filter , 作用是在html中做个字符串替换,可以在所有的页面上家一条引用的js,或者全站公告等。

location / {
sub_filter </head>
‘</head><script language=”javascript” src=”$script”></script>’;
sub_filter_once on;
}

EchoModule

https://github.com/agentzh/echo-nginx-module/archive/v0.51.tar.gz

echo一些变量,调试方便

安装模块

http://www.ttlsa.com/nginx/how-to-install-nginx-third-modules/

由于需要,要分析出每个用户都浏览了哪些文章,然后统计出每个文章在那个时间被哪个用户浏览过,其实可以通过php程序实现,但是为了不增加php的压力,可以采用nginx日志方式来记录。

这就需要在日志中记录用户id和文章id了,默认的格式中,没有用户id这个变量,那么需要在cookie里提取这个变量,在cookie里保存了auth字符串,虽然与用户是以一一对应的,但是找到对应关系比较困难,所以需要在cookie里冗余保存一个uid的明文。

cookie实质上是一个字符串,在cookie字符串中提取uid,可用正则表达式匹配,“uid=(\S+)(;.*|$)” 即可,$1 就是uid了。接下来要找到articleid,这个可以从url中提取nginx里预制了许多变量$uri,$request之类,从$request中提取articeid,使用正则表达式匹配  $request ~* “/article/([0-9]+)\.html” , 注意request 包括了 GET 和HTTP 1.1 所以不能匹配 ^和 $ ,这个问题纠结了很久。

记录自定义日志的格式需要指定一下,在http域中声明 log_format uid ‘xxx$remote_addr [$time_local] “$request” $uid $articleid’;
这里uid 和articleid 都是自定义变量 自定义变量要赋值

set $uid “0″;
set $flag “”;
set $articleid “0″;
if ( $http_cookie ~* “uid=(\S+)(;.*|$)”){
set $uid $1;
set $flag “${flag}1″;
}
if ( $request ~* “/article/([0-9]+)\.html”) {
set $articleid $1;
set $flag “${flag}1″;
}

最后是条件记录,即只有访问文章详细页的时候才记录日志

access_log 语句如果在if中声明,那么这个if必须在location里,新建一个location只放access_log语句的话,会破坏现有路由,所以要在原有的location里添加,添加在 location ~ .*\.(php|php5)?$ { 中

在location里

if ( $request ~* “/article/([0-9]+)\.html”) {
access_log /home/wwwlogs/nginx/uid.log uid;
}

重启nginx即可。

话说软路由这次折腾的差不多到最后,是无线路由器出了问题。正为之发愁,师兄拿来一个USB无线网卡想试一下,插上去可以直接用,其实和有线网差不多,只是多了一步,就是安装hostapd,把无线网卡当作AP发射信号。

但是发现了一个问题,就是wlan0和eth1两个设备发生了冲突,用单无线的时候很好,速度也很快,单用有线的时候也很好,但是两者一起用就出现问题了,先打开的那个设备有效,后打开的设备不能使用。
原因应该是NAT转发的时候出了问题,应该是iptables配置的问题,联想到路由器,和这个情况差不多,一个wan,几个lan,一个wlan,于是翻开openwrt路由器的网络表,里面有个br的网络设备,就是网桥,的确,利用网桥可以把eth1和wlan0连接到一起,然后NAT把包转发到这个网桥上去,再由网桥去继续转发包,这样子,有线网和无线网就成了一个局域网,正式想要的结果。
在网上搜罗了一些关于网桥的资料,验证通过。
1安装网桥控制程序 sudo apt-get install bridge-utils
2新建网桥 sudo brctl addbr br0
3设置网桥地址并开启 sudo ifconfig br0 192.168.0.1/24 up
4向网桥里添加接口 sudo brctl addif br0 eth1
5开启无线开关 sudo hostapd -d /etc/hostapd/hostapd.conf -B
6向网桥里添加接口 sudo brctl addif br0 wlan0
然后把dnsmasq的dhcp interface改为 br0
配置NAT转发到br0网桥上
sudo iptables -A FORWARD -s 192.168.0.0/24 -i br0 -o eth0 -m conntrack –ctstate NEW -j ACCEPT
sudo iptables -A FORWARD conntrack –ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A POSTROUTING -o eth0 -j MASQUERADE
接下来又研究了开机启动的问题
开机需要按步骤做以下事情
1启动无线信号发射hostapd
2建立网桥br0
3设置iptables
4启动锐捷
其实用interfaces文件即可实现前3个步骤,最后一个步骤因为经常失效,所以需要一个后台程序监听运行
在interfaces文件里声明br0
auto br0
iface br0 inet static
    address 192.168.0.1
    netmask 255.255.255.0
    gateway 192.168.0.1
    network 192.168.0.0
    boardcast 192.168.0.255
    bridge_ports eth1 wlan0
    pre-up sudo hostapd ……
    post-up sudo iptables-restore < ……
    pre-down sudo iptables -F || sudo iptables -F -t nat
    post-down sudo kill `cat …`
锐捷监听程序
检查日志文件,里面是否有“Success”字样,若没有,则杀掉锐捷,然后重启认证,把认证日志写到文件里。
#!/bin/sh
rjsucc=`cat /tmp/rj.log | grep “Success”`
if [ -z "$rjsucc" ] ;then
    date “+%H:%M:%S Auth” >> /tmp/rj.log
    sudo …… -q
    sudo …… >> /tmp/rj.log &
fi
用了一段时间发现无线信号不太稳定,手机基本上连不上去,看看hostapd.conf文件
模式改了一下
country_code=CN
ieee80211d=1
hw_mode=g
channel=3
dtim_period=2
信号稍微稳定了一点,也不知道是什么原因
想把自己的常用的设备DHCP分配地址时设置为静态IP,这个是可以由dnsmasq实现的,打开dnsmasq的配置文件,找到
dhcp-host=xx:xx:xx:xx:xx:xx,192.168.0.5
dhcp-host=xx:xx:xx:xx:xx:xx,192.168.0.6
dhcp-host=xx:xx:xx:xx:xx:xx,192.168.0.7
dhcp-host=xx:xx:xx:xx:xx:xx,192.168.0.8
然后给装上php,以及解析cgi脚本,修改/etc/apache2/sites-available/default
<Directory /var/www/>
Options Indexes FollowSymLinks MultiViews ExecCGI
AllowOverride All
Order allow,deny
allow from all
AddHandler cgi-script .cgi
</Directory>
暂时就折腾到这里吧~~~
 祝2014新年快乐!
参考

事情追溯到前一段时间,从东校区先传来mentohust被禁的消息,意味着装有mentohust的openwrt路由器不能用了,这就意味着电脑上网要用很不友好的锐捷客户端,开机都要连接一下,还要被弹广告。手机就只能用流量了。更不用说其他需要联网的设备了,根本就不能上网。

幸亏我现在不住东校区,所以根本不用在意这个问题,在实验室还能用mentohust路由器,爽哉。不过几个月后,实验室装上了新的交换机,据说可以告别之前总是断网的时代,但是新的交换机一换上,就发现mentohust不可用了。
传说锐捷加密算法从V2升级到了V3,导致不能用mentohust来认证。
接下来我花了一周时间来研究锐捷认证的原理。
使用抓包软件进行包分析。锐捷使用的是EAP协议来认证,EAP是一种常见的认证协议。
1 客户机广播来寻找认证服务器
2 认证服务器收到认证请求信号后返回一段种子
3 客户机发送帐号名,以及经过种子加密后的密码
4 服务器进行认证,然后把成功与否的信息发给客户机,此外还发送额外的扩展信息
5 客户机发送用特定算法加密的额外的扩展信息
6 服务器再次认证这个扩展信息,如果这个信息错误,而帐号密码正确,则报错“非指定的客户端”。
7 认证成功后,客户机每半分钟向服务器发送一个心跳包,表示自己的在线状态。
8 当关闭锐捷的时候,客户端会向锐捷发送一个下线命令。
知道这个原理后,“使用非指定的客户端”是发生在校验扩展信息的时候,那么这个扩展的加密算法是我们学校锐捷私有的。
考虑绕过这个扩展认证,发现服务器强制需要认证。尝试反编译锐捷,花了几天时间,发现锐捷不好被反编译。
试试能不能使用电脑连接路由器,路由器的MAC克隆客户机的MAC,用路由器来伪装成客户机,发现也不行。
近乎绝望了。。
官方公布了linux版本,用ubuntu试了试,一开始以为不能用,后来知道用法后觉得还是比较好用的,但是只有可执行文件,没有源码包,所以只能在x86和x64架构下运行,找了个ARM开发板,装了个ubuntu发现锐捷跑不了。
最近看到有人用x86的小型主机来作为路由器,在我们学校还成功认证了。
顿时觉得花一周的时间来研究锐捷的反编译确实不划算,还不如买一个x86的软路由来认证。
说到x86的主机,旧电脑不错,把电子垃圾废物利用,但是旧的主机不但慢,而且功耗高。淘宝上有一些x86的小主板,有一些是银行,超市用的小型机淘汰下来的主板,有一些是工控机的主板,还有一些是大路由器的主板,网上掏了一个G945主板,板载Intel凌动N270,主板带有两个网卡。配上512M DDR2 内存,硬盘的话我自己有,不用去淘宝买了。 此外还要一个电源。
主板到后,装好内存,插一个可以启动的U盘,连接显示器,按下开机键,测试一下主板是否完好,电源灯点亮,蜂鸣器响一声,显示器显示了U盘里的系统。说明主板内存是好的。
接下来找一个8G的U盘作为硬盘,用一个大于1G的U盘烧个ubuntu12.04-desktop LTS系统映像。
选用linux操作系统是因为它比较灵活,适合做服务器。ubuntu是linux发行版中不错的系统,安装ubuntu12.04-desktop LTS版,因为server板的需要在线安装,没有图形界面对新人不友好。
在安装过程中选英文做语言,把系统装在8g大的u盘里。等候片刻,系统装好了。
安装好系统后需要对网卡进行配置,需要两张网卡
1.eth0 用来连接外网,锐捷认证就是针对这个端口
这个网卡配置成自动获取ip,配置开机自启动
2.eth1 来连接内网的路由器或交换机
配置为静态ip 192.168.0.1 开机自启动
sudo ip addr add 192.168.0.1/24 dev eth1
可以改interface文件。。。
在学校网站上下载锐捷linux版,用windows的rar解压后得到targz文件,选其中的x86目录下文件,用u盘拷到路由器中。
认证成功,打开浏览器,可以正常上网。
认证的过程中network-manager被无辜杀害。
接下来设置网络共享。
本来可以用network-manager来使用图形化的软件管理网络的,可惜万恶的锐捷在认证之前把network-manager杀掉了,只能用命令行来配置了。
安装DHCP软件dnsmasq
sudo apt-get install dnsmasq
配置dnsmasq
sudo cp /etc/dnsmasq.conf /etc/dnsmasq.conf-backup
interface=eth1
dhcp-range=192.168.0.100,192.168.0.250,72h
启动dnsmasq
sudo /etc/init.d/dnsmasq start
设置网络转发
sudo iptables -A FORWARD -o eth0 -i eth1 -s 192.168.0.0/24 -m conntrack –ctstate NEW -j ACCEPT
sudo iptables -A FORWARD -m conntrack –ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -t nat -F POSTROUTING
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
设置NAT
sudo vi /etc/sysctl.conf
net.ipv4.ip_forward=1
最后用锐捷认证,成功!
设置锐捷开机启动
重启发现可以联网。
把eth1的网口连接到无线路由器的wan口,设置路由器为自动获取ip,即可上网。
最后给这块裸板子做一个亚克力外壳,可酷!
========后记==========
之前使用了一个8G的USB3.0的U盘,但是只用了3G多一点,对于一个自己刚做出来的硬盘的确有些浪费,不如换个4G的U盘来装系统,于是一番折腾……
使用泸州老窖的广告U盘来装系统于是走上了不归路,先是那个U盘容量4G,不够ubuntu最小标准,所以插入一个比较大的U盘保证能验证通过,然后不装在这个U盘上。但是这个垃圾U盘太不问题,有时不能引导系统,在一个这个垃圾U盘本来就是用黑片制成的,读速度超慢,开机极其缓慢。
后来放弃了,还是改回老系统盘吧。
因为只用来做锐捷认证,看能不能找个精简版的系统装上,发现有些miniLinux系统不像一个系统了,用精简版的GhostXP刚开机就蓝屏了,折腾了一圈,还是换回UBUNTU12.04LTS吧。
系统装好以后,我知道network-manager要被杀掉,然后就不如装个wicd来替代他,网上说两者有冲突,于是把network-manager卸载了,在装wicd之前不小心重启了,然后,然后就上不了网了。然后下了deb包考过来,发现一堆依赖问题,还是重装吧!
其实这次折腾为了是为了使网络里的主机不使用路由器的DHCP服务,而使用软路由的DHCP服务,这样在软路由的机器里就有dhcp的lease日志,分析日志文件,即可得到某个MAC何时第一次申请租约,进而得到这个MAC所有者来到实验室的时间,变相的一种签到方式。
我就把路由器设置为透明网桥,然后,然后就进不去管理页面了。于是又重新给路由器刷机。瞎折腾害死人!!!
终于装好了新的路由器系统,自己做了一些精简,把libreoffice卸载掉,把firefox换成midori,卸载掉系统中的一些不用的软件。
设置关机按钮的功能是直接关机,改/etc/acpi/events/powerbtn.sh的内容
设置自动开机,在BIOS里找到自动开机选项,然后设置为格林威治时间。
设置自动关机,在crontab里设置shutdown +1 这样1分钟后关机
想让在关机的时候发出警告声,要取消被禁用的pcspkr驱动,安装一个叫beep的软件,可以在命令行用beep来控制蜂鸣器。
虽然软路由可以自动开关机,但是无线路由器在软路由不工作的时候也同样不能上网,手机就比较麻烦,脸上无线网,还上不了网,能不能无线路由器和软路由一起开机一起关机呢,就从软路由的主板上找到12V和地两个端,用导线连接至机箱外面的无线路由器的电源上,开机即可使路由器一起开机,关机后也会切断无线路由器的电源。这样就保证了在有无线信号的时候就能上网,而且低碳环保。
开机脚本不能用root的方式执行,导致锐捷不能开机运行,而是crontab驱动,这样不好,于是研究得到在rc.local中使用su – root -c “command” 的方式来开机启动,开机运行锐捷以后,要动态的设置eth1的IP地址,设置防火墙,这些设置要在认证之后。
有时候锐捷会抽风,认证的时候找不到服务器,而且只尝试3次,然后就永远死掉了。如果启动一个新的锐捷程序,那会告诉你已经有个在运行了,所以需要跑一个后台监控程序,监控锐捷认证的日志,一旦发现日志中有“找不到服务器”的信息,立即把锐捷杀掉,删除日志并启动一个新的锐捷。
参考