[toc]

简单说一下

工作需要 需要批量修改很多机器上的配置文件,当然,只需要修改一行

配置文件格式为ini 每台机器上的配置文件都不一样,但是一定会有自己需要的那一行

这个时候,肯定会有很多人说,”这不简单吗,readline然后再判断是否包含,然后在修改就行了“

是的,问题的确就是这样做的话未免有些不雅观,都用python,那肯定要找一找有没有读取修改ini这种配置文件的python的库,最好还是python自带的,否则装库也麻烦

假设要修改的ini

[TEST1]
;; 这是注释
TEST_OPT_1 = 1
;; 这也是一处注释
TEST_OPT_2 = dotest
OPT_PATH = /home/auser 

[LISTEN]
;; 只监听本机ip
BIND_IP = 127.0.0.1
;; 监听端口
BIND_PORT = 80
MAX_FDS = 102400
SWITCH_USER = webserver

需要修改LISTEN节的BIND_PORT这个item,将其改为BIND_IP = 8080

库 ConfigParser

服务器是2.6.6 本地是2.7.x 具体忘了,但也正因此埋下了坑 坑后面说

import ConfigParser
import os
import shutil
try:
    from collections import OrderedDict as _default_dict
except ImportError:
    # fallback for setup.py which hasn't yet built _collections
    _default_dict = dict
    
class myconf(ConfigParser.ConfigParser):
    def __init__(self, defaults=None, dict_type=_default_dict,
                 allow_no_value=False):
        ConfigParserLib.ConfigParser.__init__(self,defaults, dict_type,
                 allow_no_value)

    # 这里重写了optionxform方法,直接返回选项名
    def optionxform(self, optionstr):
        return optionstr
def replaceText(path,aim,replace):
    lines = open(path).readlines()
    fp = open(path,'w')
    for s in lines:
        fp.write( s.replace(aim,replace))
    fp.close()
    
currPath = os.path.dirname(os.path.realpath(__file__))
configPath = os.path.join(currPath,"file/test.ini")
backConfigPath = os.path.join(currPath,"file/test.ini.bak")

shutil.copyfile(configPath,backConfigPath)

g_aim = ";"
g_replace = "@"
replaceText(configPath,g_aim,g_replace)

config = myconf()
config.read(configPath)
config.set('LISTEN',"BIND_PORT","8080")
config.write(open(configPath,"w"))
replaceText(configPath,g_replace,g_aim)

print "success"

这是我的替换代码

版本差异 tip

避免了很多的坑 ,但还是出问题了 服务器环境2.6.6 ConfigParser 没有allow_no_value这个属性,起作用是解析无意义的符号

慢慢来缕一缕

保存为小写问题

有人会发现 我是将ConfigParser重载了来使用的,其中,我重载了optionxform方法,因为原本的这个方法是这样的

def optionxform(self, optionstr):
    return optionstr.lower()

为了体现读取配置ini时候的大小写不敏感,python再读取对应内容的时候,都将其小写了,无论本来的内容是大写的也好,小写的也罢,都会变成小写的

于是 你使用这个玩意读取出来再存回去的配置全都会变成小写的,简直离谱

保留注释

接着 我自定义了一个replaceText方法,为什么要用替换方法来替换原本文件的内容呢?

替换的目标是;替换成@

;是ini之中的注释,因为程序是不读取注释的,没错,通过这个库,你重新生成的配置是没有一丝丝注释的——我感觉我拿脚本把所有的配置文件都跑一遍的话,会被骂死~

于是我就开始搜寻,发现需要先把注释替换为别的字符,然后开启allow_no_value=True默认值是False,这样 程序就会读取注释了,再在生成的内容中将@替换为;,我们的注释就保留成功了

真 版本差异问题探讨

前面说了 版本问题很重要

那么该怎么办呢?

个人做法---把2.7的库掏出来

在2.6的python下 用我捞出来的库 当然,记得改名称,不然就导标准库去了

相关链接

关于python:使用ConfigParser将注释写入文件

python自带的configparser模块解析ini文件

protobuf2

很久没有更新博客了,炸尸一下吧,最近遇到protobuf这个协议工具,感觉的确是非常的好用(可以说是除了我在使用的时候遇到问题然后去看官方资料的时候意外,我都是这样觉得的。)

官方教程

protobuf 教程

小白解释

简述protobuf的一些东西

message 是消息体,protobuf里面会定义很多的message来使用。这样说的话会十分的抽象,不过我们可以将其理解为封装了许多数据类型的结构体吧~

使用方法

package school;
message People{
    required string id = 1 ;
    required string name = 2 ;
    required bool sex = 3 [default = false] ; 
}
message Record{
    required string name = 1;
    required double scores = 2;
}
message Records{
    repeated Record record = 1;
}
message Student{
    required People person = 1 ;
    required id = string = 2 ;
    optional Records subject = 3;
}

以上,就是简单的写了一个学校学生信息的demo

其中people是个人信息,包含了身份证号码,名字,性别这三个必要信息,并且性别是默认为false(在这里规定性别为false的话,就是男性,否则是女性)

然后 一个学生必然拥有个人信息,也就是person,我们可以看到,在这里学生的个人信息,就是message People,意味着,我们需要写个人信息的时候,直接使用People这个message是被允许的事情。这样我们就不用重复的写,这个message拥有 id name sex这三个字段了。

然后事学生信息,学生除了message以外,还有一个id,这个id我们规定他是学号,然后还有一个subject,这个信息是他的科目的成绩对。

值得注意的是subject是一个Records的message,并且前面定义的是optional,这事除了required意外的第二个前置的字段了。

我们可以看到,required这个字段的意思是,需要的,也就是一个消息体必需的字段。

而optional的意思则是可选的,也就是说,我传给你的这个消息中,可能有optional这个字段所声明的值,可能也没有。(至于如何判断有无,后面会说)

说完这里,我们看向Records这个message,里面的字段是repeated,意思是这是个数组(当然,也是可有可无的,毕竟数组的元素可能是0嘛~)

其中包含的是Record这个message,里面包含了科目的名字,以及科目的分数。

好了,说完这写过后,我们在看message里面,会发现一个问题,每个message里面的类型后面都跟着一个 ” = 1“ , ”=2“,”=3“这类型的字段,这个表明的是proto在序列化消息时,这些消息所占据的位置,等等。

值得一提的是

如您所见,消息定义中的每个字段都有一个唯一的编号。这些数字用于在消息二进制格式中标识您的字段,一旦您的消息类型被使用,就不应更改。请注意,1 到 15 范围内的字段编号占用一个字节进行编码,包括字段编号和字段类型(您可以在协议缓冲区编码中找到更多相关信息)。16 到 2047 范围内的字段编号占用两个字节。因此,您应该为非常频繁出现的消息元素保留字段编号 1 到 15。请记住为将来可能添加的频繁出现的元素留出一些空间。

您可以指定的最小字段编号为 1,最大字段编号为 2 29 - 1 或 536,870,911。您也不能使用数字 19000 到 19999(FieldDescriptor::kFirstReservedNumberFieldDescriptor::kLastReservedNumber),因为它们是为 Protocol Buffers 实现保留的 - 如果您在.proto. 同样,您不能使用任何以前保留的字段编号。

或许我需要解释一下这段从官方机翻过来的话。

我们的数据会以消息的形式存在,当我们需要发送的时候,其实发送的都是二进制格式的消息。

这个时候,后面的编码其实是会占位置的。

而前面说过了,出了require以外声明的字段,其实是可以不存在于这个二进制的消息体之中的,所以,使用的频繁的内容,最好是占据1-15的编号,这样的话,能过有效的减少二进制编码的体积,也就能提高消息的传输效率。

同时,我们删除字段的时候,只是在协议规范里面删除了,如果你以前有将序列化过后的数据存到db或者某处,然后你又删掉了=4的字段,接着你又给另一个字段规定为=4。emmm,我们还是老老实实举个例子吧

// 修改前
message Student{
    required People person = 1 ;
    required id = string = 2 ;
    optional Records subject = 3;
}
// 修改后
message Student{
    required People person = 1 ;
    required id = string = 2 ;
    optional int64 scores = 3;
}

可以注意到,其中subject被改为scores了,scores在这里理解为总成绩,其类型是64位的int,而非一个Records的subject,可以想到是,当我们拿修改后的消息体去解析以前拥有Records的subject的这个消息(即被序列化为二进制的消息),那是一种灾难性的现场。

对于我们来说,一般是不删除某个字段,将其留在那里,或者是删除他过后,保留这个字段的编号,让后面的字段无法使用这个编号。

比如说

message Student{
    required People person = 1 ;
    required id = string = 2 ;
    reserved 3;
    reserved "subject";
    optional int64 scores = 4;
}

这样,就没有能用=3的字段了,也没有字段能过被命名为subject了。

如果想保留一个区间的字段该怎么办?

使用reserved 9 to 11这样就保留了9到11的字段,也就是9,10,11.

枚举

对于某些值,应该只有某些元素,这就是枚举。

import "mylib/myproto.proto";
enum SubjectName{
    SHUXUE = 0;
    YUWEN = 1;
    YINGYU = 2;
    DILI = 3;
}
message Course{
    required SubjectName subjectName = 1 [ default = SHUXUE];
    optional double scores = 2;
    optional int32 number = 3;
    optional Time time; // 由myproto.proto导入
    optional Addres addres; // 由myproto.proto导入
}

在上面,我用拼音写出了科目的枚举,当然,科目不止这些,但我也就不一一敲出来了。

对于一个课程,我们需要知道这是个什么课程。

每个课程有一个属于自己的枚举,其中我们假设课程默认为数学。

这样的话,就不会存在我们不知道课程。

如果后续由其他的课程的话,我们可以添加新的枚举值——在原来的基础上,这事不受任何影响的。

说到这里,protobuf的具体协议其实就已经说清楚了,其中protobuf支持变量的类型,以及更多细节都在开头给的protobuf链接哪里官方教程

接下来会讲C++之中具体的应用,当然,那是在另一篇文章的事情了。

drogon学习记录

[toc]

安装 ubuntu安装drogon命令:

apt install git gcc g++ cmake libjsoncpp-dev uuid-dev openssl libssl-dev zlib1g-dev postgresql-all libmariadbclient-dev postgresql-all -y 
git clone https://github.com/an-tao/drogon
cd drogon
git submodule update --init
mkdir build
cd build
cmake ..
make && sudo make install

ps 这条命了最后一个装的是pg的数据库,如果是mysql 和 sqllit或者其他的话请上drogon官网自己看一下装什么驱动

macos安装drogon命令:

brew install drogonframework/drogon/drogon

因为已经装好了又不好卸载 所以就暂时不写了

drogon文件上传

文件上传的一般来说使用formdata

当然 也是可以用json的,只不过需要把文件转换成base64码而已

formdata文件上传

引用官方的列子

[](const HttpRequestPtr &req,
           std::function<void(const HttpResponsePtr &)> &&callback) {
            MultiPartParser fileUpload;
            if (fileUpload.parse(req) != 0 || fileUpload.getFiles().size() != 1)
            {
                auto resp = HttpResponse::newHttpResponse();
                resp->setBody("Must only be one file");
                resp->setStatusCode(k403Forbidden);
                callback(resp);
                return;
            }

            auto &file = fileUpload.getFiles()[0];
            auto md5 = file.getMd5();
            auto resp = HttpResponse::newHttpResponse();
            resp->setBody(
                "The server has calculated the file's MD5 hash to be " + md5);
            file.save();
            LOG_INFO << "The uploaded file has been saved to the ./uploads "
                        "directory";
            callback(resp);
        }

官方在这里是把其写成了一个匿名函数,放在cli创建的代码里,其实就是contorllers里面的一个实现。

可以看到的是,通过MultiPartParser对req进行解析可以判断里面是否有文件,通过MultiPartParser::getFiles().size()的方法可以获取文件的数目

MultiPartParser::getFiles()可以获得文件数组

其中的一个对象,可以通过save方法进行保存,save方法可以传字符串,也可以不传。

如果不传字符串,文件默认保存在程序运行的目录。

如果传入字符串,则文件保存在字符串指定的目录,如果当前系统没有该目录,程序会自动生成该目录