月度归档:2016年05月

Python学习笔记(三)

闭包

如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。它只不过是个“内层”的函数,由一个名字(变量)来指
代,而这个名字(变量)对于“外层”包含它的函数而言,是本地变量。

看一个例子:

#!/usr/bin/python

def squ():
    fs = []
    for i in range(1, 4):    # range(1, 4) = [1, 2, 3]
        def f():
            return i*i
        fs.append(f)
    return fs

f1, f2, f3 = squ()    # same as f1 = squ()[0]

print f1()
print f2()
print f3()

执行这个函数,你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:

9
9
9

这是为什么呢?

首先明确,调用squ()后,squ()中存放的是三个f函数,而不是f函数计算出来的结果。

闭包 = 函数 + 引用环境,也就是说,当形成一个闭包之后,放进闭包的并不是具体的值。在上面的例子中,闭包中应该只包含变量i的地址,告诉程序当他被调用时这个i应该从哪里找,此时并不涉及i的值。只有当真正调用时,才根据此时i的值算出最终结果,而此时在返回3个函数之后,i的值已经成为3了,所以当我们开始调用函数时,返回的值就都是9了。

换一种写法理解起来应该容易

#!/usr/bin/python

def squ():
    fs = []
    for i in range(1, 4):
        def f():
            return i*i
        fs.append(f)
    return fs

print squ()[0]()

这样执行打出的结果将是:

9

当调用squ函数的时候,squ()这个list中存储的值是三个f函数,这三个f函数指向同一个变量i的地址。所以结果是一样的。

那么如何用第一个函数返回1, 4, 9呢? 只需要在squ函数被调用的时候,list中存储的f函数指向的参数值不同就行了,例如下面的写法:

#!/usr/bin/python

def squ():
    fs = []
    for i in range(1, 4):
        def f(j=i):
            return j*j    # if return i*i, answer will be 9
        fs.append(f)
    return fs

f1, f2, f3 = squ()

print f1()
print f2()
print f3()

f函数的值不再与i变化,而是记录下当前i的值用自己的参数j表示。

Let’s Encrypt自动续期脚本(更新)

2017-06-06 更新


最新版本的Let’s Encrypt已经可以通过命令自动更新了,只需执行一条Renew命令即可。具体命令参照 Certbot官网,选择实际的操作系统版本和服务器版本即可找到。

以下是原文(2016-5-25)内容


Let’s Encrypt自动续期脚本这篇文章中,我给出了一个Let’s Encrypt的自动续期脚本。随着Let’s Encrypt项目走出测试阶段进入正式服务阶段,脚本代码也发生了变化。按照原来的脚本执行时,虽然还是能够完成续期操作,但是官方给出了一个警告信息:

20160525182433

内容是说之前的letsencrypt-auto这个脚本已经过期了,并不推荐使用。现在是使用一个叫做certbot-auto的脚本来实现。于是我打开官网的说明文档,果然也已经改成了certbot方式,详见官网Get Start文档。那这里也给出更新方法。

首先去下载新的程序代码:

cd /usr
git clone https://github.com/certbot/certbot
cd /usr/certbot

在这个目录下我们可以看见一个certbot-auto文件,我们将用它替换letsencrypt-auto。

修改之前的自动更新脚本(之前的脚本请移步此文: Let’s Encrypt自动续期脚本)

cd /usr/shell
vi letsencrypt-auto-renew.sh

修改成如下:

#!/bin/bash
cd /usr/certbot
./certbot-auto certonly -a webroot --renew-by-default --config /etc/letsencrypt/auto-renew.ini
service nginx reload

执行这个脚本,如果正常运行则修改成功

root@localhost:/usr/shell# ./letsencrypt-auto-renew.sh 
Checking for new version...
Requesting root privileges to run certbot...
   /root/.local/share/letsencrypt/bin/letsencrypt certonly -a webroot --renew-by-default --config /etc/letsencrypt/auto-renew.ini

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/zivers.com-0001/fullchain.pem. Your cert will
   expire on 2016-08-23. To obtain a new version of the certificate in
   the future, simply run Certbot again.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

 * Reloading nginx configuration nginx                                                                      [ OK ]

又拍云CDN自定义域名加速及HTTPS支持

之前网站一直使用的是七牛的CDN服务,使用体验一直不错,虽然提供的是免费的服务但是依旧十分可靠。在网站实现全站HTTPS之后,七牛的问题就暴露了出来:七牛支持自定义二级域名作为CDN域名,但是不支持HTTPS下的二级域名自定义。这样,我的网站源码里可能就充斥着,例如https://o37cfn45w.qnssl.com,这样丑陋的链接。这时,我发现了另一家CDN提供商,又拍云。在它的介绍里是这样描述的:支持上传自定义的SSL证书,支持自定义CDN域名。这不正是我需要的么,于是我注册并开始了试用。

这里要吐槽一下,又拍云的文档就是一坨翔。看第一遍啥都没看明白,看完第二遍完全也不知道该怎么做。纯属是把CDN和SSL的一些概念性的东西贴上了,有种给你一本字典就让你写书的蛋疼感。好在这方面我还算有经验,大概摸索着花了不到一小时还是弄明白了。下面是详细的部署过程:

准备工作

  1. 域名已经备案(国内的服务基本这个跑不了)。
  2. SSL证书。如果需要支持HTTPS的CDN服务的话,需要自备证书上传。证书需要含有你用来作为CDN的二级域名(例如我的证书就包含三个域名:zivers.com www.zivers.com static.zivers.com)。如果使用Let’s Encrypt证书的话,下面有具体方法。
  3. 又拍云帐号。最好往里头放点钱备用,我先充了10块。

创建服务

登录又拍云帐号,选择服务标签,点击创建服务。

20160525151802

这里需要输入一个服务名称。随便写不重复的就好。比如写的是www-zivers-com,之后就会给个www-zivers-com.b0.upaiyun.com的三级域名给你。点击下一步。

20160525152930

这一步会涉及到一些专业名词,包括HTTP头,回源,host等。如果只是单纯的CDN而已并不需要理会太多。这里大概说下这么选的理由:

  • 自主源站:就是资源从自己的主站上获取。如果选后者则需要将资源上传到又拍云的空间里
  • 加速域名:填入给CDN用的二级域名,比如这里的static.zivers.com
  • 回源Host:选择域名跟随是取到的是来自域名的Host头部信息
  • 回源方式:我已经全站HTTPS自然是选HTTPS方式。没有HTTPS支持的话选第一个。都有选第三个,选第三个的话注意线路配置里头的端口号要对应
  • 回源线路:根据需求选。回源地址写自己网站就行,端口号如果是HTTPS就写443,HTTP就写80

点击下一步。

添加管理员,没啥说的。然后点击下一步就完成了。

这时候系统会自动分配给你一个域名,也就是上面说的www-zivers-com.b0.upaiyun.com。由于我配置的是HTTPS的CDN,而现在并没有为这个三级域名配置HTTPS,所以现在直接访问这个域名是无法获取到内容的。

SSL证书配置

现在去配置HTTPS,点击工具箱,选择SSL服务

20160525153519

这里有个“又拍云默认HTTPS证书”,这个证书就是用来给又拍云三级域名提供HTTPS加持的证书。点击管理,然后勾选开启HTTPS访问

20160525153702

保存之后,这个三级域名就可以直接访问了。如果不想绑定自主域名,到这一步就算结束了,只需要在网站上将静态内容指向这个三级域名即可。

如果需要绑定自己的二级域名(static.zivers.com),那么继续下面的步骤。

绑定域名

首先进入服务页面,找到刚才创建的服务(www-zivers-com),点击功能配置。在这个页面可以看到刚才自动生成的又拍云三级域名和我们自己添加的二级域名。

20160525153157

这里有段提示:“绑定域名后,请在域名服务商处将其 CNAME 配置为:www-zivers-com.b0.aicdn.com”

那么就去DNS解析商那加上这条cname记录。等待生效后,访问https://static.zivers.com,发现还是无法访问。原因和之前的又拍云三级域名无法访问一样,还没有添加这个域名的SSL证书。这个证书是需要由我们自己上传的。和之前一样,到工具箱的SSL服务页面,点击“添加SSL证书”。

20160525154648

把证书和私钥都填上保存,之后这个页面就多了一个我们自己的证书。找到这个证书,点击“管理”,将我们的自主域名(static.zivers.com)勾选就行了

20160525154903

至此,又拍云的自主域名CDN和HTTPS支持就添加完毕了。访问https://static.zivers.com也能够获取到正确的内容。

Let’s Encrypt配置

在之前的文章中有过对Let’s Encrypt的说明和教程,这里就不多说了。之前我的证书中只包含了两个域名zivers.com和www.zivers.com,并没有static.zivers.com。但是在CDN的HTTPS配置中需要用到这个域名,所以这里需要进行配置。首先执行下面的命令:

cd /usr/letsencrypt 
./letsencrypt-auto certonly --standalone

然后在简单的交互框中输入我的三个域名,然后继续。这里报错了

Failed authorization procedure. static.zivers.com (tls-sni-01): urn:acme:error:tls :: The server experienced a TLS error during domain verification :: Failed to connect to 165.254.60.146:443 for TLS-SNI-01 challenge

IMPORTANT NOTES:
 - The following errors were reported by the server:

   Domain: static.zivers.com
   Type:   tls
   Detail: Failed to connect to 165.254.60.146:443 for TLS-SNI-01
   challenge

   To fix these errors, please make sure that your domain name was
   entered correctly and the DNS A record(s) for that domain
   contain(s) the right IP address. Additionally, please check that
   you have an up-to-date TLS configuration that allows the server to
   communicate with the Certbot client.

原因很明显,我将static.zivers.com这个域名用cname解析到了又拍云的三级域名,并不是我的服务器,这样认证当然是失败的。所以,我去DNS解析商处先将static.zivers.com暂时用A解析解析到服务器IP,之后生成证书。最后再解析回三级域名即可。

效果

站点的静态内容已经全部使用static.zivers.com这个域名访问了:

20160525160804

为TinyMCE编辑器加上拼写检查

TinyMCE编辑器是常用的一款轻量级的基于浏览器的WYSIWYG(所见即所得)富文本编辑器。它支持目前流行的各种浏览器,由JavaScript写成。功能配置灵活简单(两行代码就可以将编辑器嵌入网页中),支持AJAX。

作为文本编辑器,拼写检查是一个十分重要的功能,但是在TinyMCE中并不是默认开启的,需要我们在初始化的时候进行一些配置。在官网的说明文档中提供了一种基于插件的解决方案,插件内含在了编辑器代码中,只需要在初始化的时候引用即可。代码如下:

tinymce.init({
    selector: "textarea",  // change this value according to your HTML
    plugins: "spellchecker",
    toolbar: "spellchecker",
});

但是我操作后并没有成功,当我手动点击Spellchecker按钮时,返回了一个403错误。STFW后,我在Stackoverflow上找到了一个原因和解决方案

According to what I’ve found elsewhere, the spellchecker plugin was powered by Google service – which has been retired. So at this time there does not appear to be an integrated TinyMCE spellchecker solution.

由于这个编辑器是基于浏览器的,Google将旧的spellchecker支持的服务给去掉了,但是我们可以使用浏览器内置的拼写检查服务,代码如下:

tinymce.init({
    browser_spellcheck : true,
});

 

为什么要序列化

什么是序列化

序列化就是一种处理对象流的机制。

所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O),我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间 (注:要想将对象传输于网络必须进行流化)!在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的!

问题的引出

如上所述,读写对象会有什么问题呢?比如:我要将对象写入一个磁盘文件而后再将其读出来会有什么问题吗?别急,其中一个最大的问题就 是对象引用!举个例子来说:假如我有两个类,分别是A和B,B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); },这时在内存中实际上分配了两个空间,一个存储对象a,一个存储对象b,接下来我们想将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题!因为 对象b包含对对象a的引用,所以系统会自动的将a的数据复制一份到b中,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个 空间,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a的数据的话,那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的!

序列化机制的解决方案

  1. 保存到磁盘的所有对象都获得一个序列号(1, 2, 3等等)
  2. 当要保存一个对象时,先检查该对象是否被保存了。
  3. 如果以前保存过,只需写入”与已经保存的具有序列号x的对象相同”的标记,否则,保存该对象

序列化的实现

将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

在序列化的过程中,有些数据字段我们不想将其序列化,对于 此类字段我们只需要在定义时给它加上transient关键字即可,对于transient字段序列化机制会跳过不会将其写入文件,当然也不可被恢复。但 有时我们想将某一字段序列化,但它在SDK中的定义却是不可序列化的类型,这样的话我们也必须把他标注为transient,可是不能写入又怎么恢复呢? 好在序列化机制为包含这种特殊问题的类提供了如下的方法定义:

private void readObject(ObjectInputStream in) throws 
IOException, ClassNotFoundException;
private void writeObject(ObjectOutputStream out) throws 
IOException;

(注:这些方法定义时必须是私有的,因为不需要你显示调用,序列化机制会自动调用的)

使用以上方法我们可以手动对那些你又想序列化又不可以被序列化的数据字段进行写出和读入操作。

下面是一个典型的例子,java.awt.geom包中的Point2D.Double类就是不可序列化的,因为该类没有实现Serializable接口,在我的例子中将把它当作LabeledPoint类中的一个数据字段,并演示如何将其序列化

import java.io.*;
import java.awt.geom.*;
public class TransientTest
{
         public static void main(String[] args)
         {
                 LabeledPoint label = new LabeledPoint("Book", 5.00, 5.00);
                 try
                 {
                         System.out.println(label);// 写入前
                         ObjectOutputStream out = new ObjectOutputStream(new
                         FileOutputStream("Label.txt"));
                         out.writeObject(label);
                         out.close();
                         System.out.println(label);// 写入后
                         ObjectInputStream in = new ObjectInputStream(new
                         FileInputStream("Label.txt"));
                         LabeledPoint label1 = (LabeledPoint) in.readObject();
                         in.close();
                         System.out.println(label1);// 读出并加1.0后
                 }
                 catch (Exception e)
                 {
                         e.printStackTrace();
                 }
         }
}
class LabeledPoint implements Serializable
{
         public LabeledPoint(String str, double x, double y)
         {
                 label = str;
                 point = new Point2D.Double(x, y);
         }
         private void writeObject(ObjectOutputStream out) throws IOException
         {
                
                 out.defaultWriteObject();
                 out.writeDouble(point.getX());
                 out.writeDouble(point.getY());
         }
         private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
         {
                
                 in.defaultReadObject();
                 double x = in.readDouble() + 1.0;
                 double y = in.readDouble() + 1.0;
                 point = new Point2D.Double(x, y);
         }
         public String toString()
         {
                 return getClass().getName()+ "[label = " + label+ ", point.getX() = " + point.getX()+ ", point.getY() = " + point.getY()+ "]";
         }
         private String label;
         transient private Point2D.Double point;
}

总结

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为对象。

把对象转换为字节序列的过程称为对象的序列化。 把字节序列恢复为对象的过程称为对象的反序列化。 说的再直接点,序列化的目的就是为了跨进程传递格式化数据

参考

原文链接:为什么要序列化!什么时候序列化

Shell字符串操作

在做shell批处理程序时候,经常会涉及到字符串相关操作。有很多命令语句,如:awk,sed都可以做字符串各种操作。 其实shell内置一系列操作符号,可以达到类似效果,大家知道,使用内部操作符会省略启动外部程序等时间,因此速度会非常的快。

$string的长度

${#string}

在$string中, 从位置$position开始提取子串

${string:position}

在$string中, 从位置$position开始提取长度为$length的子串

${string:position:length}

从变量$string的开头, 删除最短匹配$substring的子串

${string#substring}

从变量$string的开头, 删除最长匹配$substring的子串

${string##substring}

从变量$string的结尾, 删除最短匹配$substring的子串

${string%substring}

从变量$string的结尾, 删除最长匹配$substring的子串

${string%%substring}

使用$replacement, 来代替第一个匹配的$substring

${string/substring/replacement}

使用$replacement, 代替所有匹配的$substring

${string//substring/replacement}

如果$string的前缀匹配$substring, 那么就用$replacement来代替匹配到的$substring

${string/#substring/replacement}

如果$string的后缀匹配$substring, 那么就用$replacement来代替匹配到的$substring

${string/%substring/replacement}

.blog域名将于年底开启注册

20160514213755

根据WordPress官网在5月12日的新闻,WordPress.com公司所持有的.blog顶级域名将于年底开启注册(原文链接)。根据之前的消息,.blog域名由WordPress.com的母公司Automattic所拥有。

昨日宣布已获得 .blog 域名的审查和出售、注册的权利。一年前,此域名曾被拍卖给一个神秘买家。Automattic 的一位发言人表示旗下子公司联合一家收购域名的公司 Primer Nivel 达成合作,共同出资 190 万美元获得该域名的运营权。(VR报道 )

20160514213731

根据官网提供的时间表,.blog域名将于今年8月开启SUNRISE注册,即商标持有人注册。在此阶段,拥有商标(品牌)的企业将能够提前注册与自己品牌相关的域名。之后是10月份开启的LANDRUSH注册阶段,这个阶段的规则解释如下:

LANDRUSH阶段的域名,若只有一个合格的申请者申请,那么域名将分配给该申请者;若同一个域名多个合格的申请者,所有合格的申请者将安排网上拍卖,拍卖将采用英式的拍卖模式,并且具有合理的透明度。

最后是GENERAL AVAILABILITY阶段,这个阶段预计于11月开始,这个阶段开始后,普通用户也可以进行域名注册,即开放注册。

Mysql导出表结构及表数据

命令行下具体用法如下: mysqldump -u用户名 -p密码 -d 数据库名 表名 脚本名;

导出数据库为dbname的表结构(其中用户名为root,密码为dbpasswd,生成的脚本名为db.sql)

mysqldump -uroot -pdbpasswd -d dbname >db.sql;

导出数据库为dbname某张表(test)结构

mysqldump -uroot -pdbpasswd -d dbname test>db.sql;

导出数据库为dbname所有表结构及表数据(不加-d)

mysqldump -uroot -pdbpasswd dbname >db.sql;

导出数据库为dbname某张表(test)结构及表数据(不加-d)

mysqldump -uroot -pdbpasswd dbname test>db.sql;

ImageMagick远程代码执行漏洞

漏洞描述:

据ImageMagick官方,目前程序存在一处远程命令执行漏洞(CVE-2016-3714),当其处理的上传图片带有攻击代码时,可远程实现远程命令执行,进而可能控制服务器,此漏洞被命名

ImageTragick。ImageMagick是一款开源图片处理库,支持PHP、Ruby、NodeJS和Python等多种语言,使用非常广泛。包括PHP imagick、Ruby rmagick和paperclip以及NodeJS imagemagick等多个图片处理插件都依赖它运行。可能的影响范围包括各类流行的内容管理系统(CMS)。

影响影响范围:

  1. 调用ImageMagick的库实现图片处理和渲染的应用。
    ImageMagick 为多种语言提供了api,具体可参考http://www.imagemagick.org/script/api.php
  2. 很多流行的内容管理系统(CMS)使用了ImageMagick ,例如 WordPress 的图片处理插件已被证实存在远程命令执行的漏洞(Author 及以上权限用户执行)。其他例如MediaWiki、phpBB和vBulletin 使用了ImageMagick 库生成缩略图,还有一些程序如LyX使用ImageMagick转换图片格式。以上应用可能受到此漏洞影响。
  3. 如果通过shell 中的convert 命令实现一些图片处理功能,也会受到此漏洞影响。

漏洞等级:高危

漏洞验证方法:

若系统中安装使用了ImageMagick,本地执行如下命令:

convert 'https://example.com"|ls "-la' out.png

若ls -la 命令成功执行,说明存在漏洞。未执行ls 命令,并报错,说明不受影响。
修复建议(临时):

  1. 由于远程命令执行时,命令代码是包含在图片中上传的,所以在图片上传时需要对图片内容进行检验。
  2. Linux临时防护方案 :编辑 /etc/ImageMagick/policy.xml ,在 之间增加以下几行
<policy domain="coder" rights="none" pattern="EPHEMERAL" />
<policy domain="coder" rights="none" pattern="URL" /> 
<policy domain="coder" rights="none" pattern="HTTPS" /> 
<policy domain="coder" rights="none" pattern="MVG" />
<policy domain="coder" rights="none" pattern="MSL" />

漏洞曝出后,我在公司的Thumbor服务器上运行了测试指令,发现返回如下:

20160509103029

ls -la命令被成功执行,即存在漏洞。按修复方案执行后,返回错误。修复成功。

20160509103218