什么是 REST

2017/04/05

1 REST的概念

Roy Thomas Fielding 博士在2000年 发表了论文 Architectural Styles and the Design of Network-based Software Architectures (《架构风格与基于网络的软件架构设计》) 。在这篇论文中,首次系统地阐述了 REST 的架构风格和设计思想。REST,全称为(Resource) Representational State Thransfer ,常见的翻译为”表现层状态转移”,它省略了主语:资源。通俗来讲,即资源在网络中以某种表现形式进行状态转移。

  • Resource: 资源,即数据。泛指 Web 上一切可以识别、命名、可访问或处理的实体,如 HTTPPage,音频文件等。使用统一资源定位符指(URI)向资源.
  • Representational:某种表现形式。如 JSON,XML,二进制流等
  • State Transfer: 状态变化。通过 HTTP 动词实现。

在互联网发展的早期,网页的前后端是融合在一起的,如JSP,PHP.互联网发展的今天,客户端硬件设备层出不穷,而不同的Client又需要展示相同的商业逻辑。REST 接口可以为为不同的设备提供一套统一的接口。

简单来说, REST 即为: 使用URL定位资源,使用HTTP原语描述操作

HTTP原语,指 HTTP提供的原始方法,如 GET\POST\DELETE\HEAD etc

2 RESTful设计风格

RESTful 不是架构,而是一种架构风格,它提供了一些设计原则和约束条件。 REST 架构风格最重要的约束有:

  1. C/S架构。 Client/Server 架构形式提供了基本的分布式,客户端发走请求,服务端响应可拒绝请求。使用 HTTP Status Code 传递 Server 状态信息,如果出错则返回错误信息,则客户端处理异常。
  2. 无状态。通信的传话状态应该全部由客户端维护。即请求中应该包括全部的必要信息。
  3. 缓存。无状态不胜表示可能出现重复的请求。事实上有些请求只需要第一次完成执行,其余次请求皆可以享用这次请求的成果。缓存可以抵消一部分无状态带来的影响。
  4. 统一接口。这意味着每一个REST应用都共享一种能用架构。
  5. 分层系统:将系统划分为几个部分,每个部分负责单一职责。然后通过上层对下层的依赖和调用组成一个完成的系统。通常将系统划分为:应用层、服务层、数据访问层。

如果一个架构满足 REST 原则 ,那么就可以称它为 RESTful 架构。

API 发布之后,就很难再修改。所以在设计API时需要遵循一定规范,且又需要灵活友好。
继续阅读

Note , , 7,179

C++11/14 新特性 (多线程)

2017/03/28

在 C++11 之前 ,C++ 标准并没有提供统一的并发编程标准,也没有提供语言级别的支持。这导致我们在编写可移植的多线程程序时很不方便,往往需要面向不同的平台进行不同的实现,或者引入一些第三方平台,如Boost,pthread_win32 等。 从C++11开始 ,对并发编程进行了语言级别的支持,使用使用C++进行并发编程方便了很多。这里介绍C++11并发编程的相关特性。

1 线程

1.1 线程的创建

std::thread 的构造函数如下:

template< class Function, class... Args > 
explicit thread( Function&& f, Args&&... args );

我们只需要提供线程函数或函数对象,即可以创建线程,并可以同时指定线程函数的参数。

#include <iostream>
#include <thread>
using namespace std;

void func(const string& str){
    cout<<str<<endl;
}

class FuncCls{
public:
    void operator()(){
        cout<<"Funcls called"<<endl;
    }
};

int main(void){
    thread t1(func,"Hello World");
    FuncCls fcls;
    thread t2(fcls);

    if(t1.joinable()){
        t1.join();
    }
    t2.join();
    return 0;
}

join函数将会阻塞线程,直到线程函数执行完毕,主线程才会接着执行。如果线程函数有返回值,返回值将被忽略。 在使用线程对象的过程中,我们需要注意线程对象的生命周期。如果线程对象先于线程函数结束,那么将会出现不可预料的错误。可以通过线程阻塞的方式来等待线程函数执行完(join),或让线程在后台执行。 如果不希望线程被阻塞,可以调用线程的 detach() 函数,将线程与线程对象分离。但需要注意的是,detach 之后的线程无法再使用join来进行阻塞了,即detach之后的线程,我们无法控制了。当我们不确定一个线程是否可以join时,可以先使用 thead::joinable() 来进行判断。

另外,我们还可以通过 std::bind, lambda 来创建线程(其实就是使用函数对象创建线程)。

线程不可以被复制,但是可以被移动:
继续阅读

谁动了我的精度 — 浮点数运算的问题

2017/03/23


1 引子

同事写的程序出现了点问题。调试发现,错误出现在一个 if 语句上:

// ...
// float a = 0.2, b=0.4;
if(a + b == 0.6)
// ...

这个 if 表达式被判定为 false, 程序没有按照预订执行下去。0.2 + 0.4 = 0.6,这还会有错吗?同事表示其小学数学还是很过关的。 是的,在人类的认知里,这毫无疑问是正确的,但是在计算机的认知里,就不一定了。这需要我们了解浮点数这种数据类型。

2 浮点数

2.1 用二进制表示小数

首先我们来想一下如何来表示一个十进制整数 d:

$$ d_m d_{m-1} … d_1 d_0 d_{-1} d_{-2} … d_{-1}, d\in [0,9] $$

这个表示方法描述的数值 d 的定义如下:

$$ d = \sum_{i=-1}^m{10^i \times d_i} $$

同样,引申到小数,小数点左边的数字是 10 的非负幂,得到整数部分;小数点右面的数字是 10 的负幂,得到小数部分。

例如: \(12.34_{10}\) 所表示的字为: \( 1 \times 10^{1} + 2 \times 10^{0} + 3 \times 10^{-1}
+ 4 \times 10^{-2} =12 \frac{34}{100} \)

类似地我们考虑一个二进制数 b 的表示:

$$ b_{m} b_{m-1} … b_{1} b_{0} b_{-1} … b_{-n} ,b\in[0,1] $$

它的定义如下:
$$ b = \sum_{i=-n}^{m}2^i \times d_i $$

如 \(10.11_2\) 表示数字: \(1 \times 2^1 + 1 \times 2^{0} + 1 \times 2^{-1} + 1 \times 2^{-2} = 2 \frac{3}{4} \)

继续阅读

Note 6,683

C++11/14 新特性(function/bind 可调用对象包装器与绑定器)

2017/02/28

1 可调用对象

在 C++ 中,可调用对象一般是指:

  • 一个函数指针
  • 一个重载 () 操作符的类对句(仿函数)
  • 一个可被转换为函数指针的类对象
  • 一个类成员函数指针
#include <iostream>

void func(void)
{
    std::cout<<"func(void) called"<<std::endl;
}

struct Foo
{
    void operator()(void)
    {
        std::cout<<"Foo::()(void) called"<<std::endl;
    }
};

struct Bar
{
    using ft_t = void(*)(void);
    static void func(void)
    {
        std::cout<<"Bar::func(void) called"<<std::endl;
    }

    operator ft_t(void)
    {
        return func;
    }
};

struct A
{
    int m_a;
    void mem_func(void)
    {
        std::cout<<"A:memfunc(void) called and m_a="<<m_a<<std::endl;
    }
};


int main() {
    //1.函数指针
    void(* func_ptr)(void) = &func;
    func_ptr();

    //2.仿函数
    Foo foo;
    foo();

    //3.可被转换为函数指针的类对象
    Bar bar;
    bar();

    //4.类成员函数指针或类成员指针
    void (A::*mem_func_ptr)(void) = &A::mem_func;
    int A::*mem_obj_ptr = &A::m_a;
    A aa;
    (aa.*mem_obj_ptr) = 123;
    (aa.*mem_func_ptr)();

    return 0;
}

上例中的这些对象(func_ptr,foo,bar,mem_func_ptr,mem_obj_ptr) 均可称之为 “可调用对象”。相应的,其类型可被称作“可调用类型”。注意这里只有成员函数有成员函数指针而没有函数类型或函数引用类型,这是因为函数类型并不能直接用于定义对象,而函数引用或以看做一个 const 的函数指针。 可调用对象具有比较统一的调用形式,即使用括号操作(除成员函数指针),而定义的方法各不一样。这样我们在试图使用统一的方式保存,或传递一个可调用对象时,会十分烦琐。

2 std::function 可调用对象包装器

std::function 是一个类模板,它可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟调用它们。
继续阅读

Coding , 8,167

python 操作 MS Word

2017/01/12

1 概述python-docx-example-docx-01

python 为脚本自动化操作 Word 提供了可能。最为常用是 python-docx .使用它可以方便地创建或更新 Microfoft Word(.docx) files.

下图是其官网给出的一个使用 python-docx 创建的 word 文档的 Demo:

文档地址:http://python-docx.readthedocs.io/en/latest/index.html

github: https://github.com/python-openxml/python-docx
这是官网给出的代码:

from docx import Document
from docx.shared import Inches

document = Document()

document.add_heading('Document Title', 0)

p = document.add_paragraph('A plain paragraph having some ')
p.add_run('bold').bold = True
p.add_run(' and some ')
p.add_run('italic.').italic = True

document.add_heading('Heading, level 1', level=1)
document.add_paragraph('Intense quote', style='IntenseQuote')

document.add_paragraph(
    'first item in unordered list', style='ListBullet'
)
document.add_paragraph(
    'first item in ordered list', style='ListNumber'
)

document.add_picture('monty-truth.png', width=Inches(1.25))

table = document.add_table(rows=1, cols=3)
hdr_cells = table.rows[0].cells
hdr_cells[0].text = 'Qty'
hdr_cells[1].text = 'Id'
hdr_cells[2].text = 'Desc'
for item in recordset:
    row_cells = table.add_row().cells
    row_cells[0].text = str(item.qty)
    row_cells[1].text = str(item.id)
    row_cells[2].text = item.desc

document.add_page_break()

document.save('demo.docx')

2 安装 python-docx

可以使用 pip 或 easy_install 来进行安装

pip install python-docx

或

easy_install python-docx

也可以直接下载安装文件来进行安装:

tar xvzf python-docx-{version}.tar.gz
cd python-docx-{version}
python setup.py install

要求Python 版本在2.6 以上或 3.3 以上,lxml 版本在 2.3.2及以上。 在 Windows 10 / Python 2.7 环境下安装时出现 lxml3.7.2 安装失败的问题,可降低版本进行尝试:

pip install lxml==3.4.2

3 快速上手

3.1 打开文档

from docx import Document
document = Document()

新建一个空的 word 文档。当然,也可以打开一个已存在的 word 文档,只要传入相应的路径就好。

3.2 添加段落

继续阅读

Note 5,030

C++ API 中的版本控制

2017/01/06

1 应该提供 API 版本号信息

//version.h
#include <string>
#define API_MAJOR 1
#define API_MINOR 2
#define API_PATCH 0

class version
{
public:
    static int GetMajor();
    static int GetMinor();
    static int GetPatch();
    static std::string GetVersion();
    static bool IsAtLeast(int major,int minor,int patch);
    static bool HasFeature(const std::string& name);
}

这个类提供了单独返回当前版本号的主、次、补丁版本号的的访问函数。它们返回各 define 定义的值。GetVersion 方法向用户返回友好的字符串版本号信息。 当用户相比较版本号时,他们通常不关心版本号本身,而是想知道某些特性在该版本的API中是否存在。HasFeature() 方法 不是告诉用户哪个版本的 API 引入了哪些特性,而是让亿们直接测试某个特性是否可用。

2 软件分支策略

一般的大型项目通常会涉及到某种形式的分支策略,这需要同步开发、维护不同的软件版本发布。我们将讨论为项目选择分支策略和方针时需要考虑的一些事项。

2.1 分支策略

每个软件都需要一条 trunk (主干)代码路线,它是cppAPIversion软件项目源码
的持久库。对于对于每次版本的发布
,可以从主干进行。每次新版本的开发,可以从主干的代码添加分支,而使主干的代码不受开发的影响而保持稳定。右图是一种常见的分支策略。

2.2 API与并行分支

在API发布以后,对其所有的更改都应该表现为一个连续的过程,即不发布不兼容的非线性版本的API,一个版本的API应该是前一个版本功能的严格超集。在大型项目中,通常会有几条并行的代码分支在进行同时开发,这就会产生若干个并行维护的API版本。因此,工作在不同并行分支上的团队互不引入不兼容的特性是非常重要的。下列方法可以帮忙处理这种潜在的问题:
继续阅读

Coding 8,096

C++11/14 新特性 (使用 chrono 进行时间处理)

2017/01/03

在c++11以前,我们获取当前时间的时间戳,需要借助于第三方库,如 boost ,或者针对不同的操作做不同的处理:

#ifndef _MSC_VER
#include <sys/time.h>
#endif

long long GetTimeSpan()
{
    long long llTimeMs = 0L;
#ifdef _MSC_VER
    timeb tb;
    ftime(&tb);

    lTimeMs = tb.time * 1000 + tb.millitm;

#else
    timeval tmv;
    gettimeofday(&tmv,NULL);


    llTimeMs = (long long)(tmv.tv_sec) * 1000L + tmv.tv_usec / 1000;

#endif
    return llTimeMs;
}

而在c++11中,这个问题得到解决。 c++11 标准库中提供 chrono库,用来处理时间相关的数据。

1 duration 记录时长

duration 表示一段时间间隔。其原型:

template <class _Rep, class _Period>
class duration;

_Rep 为一个数值类型,如 int, long, 用来表示时钟数的类型。 _Period 为一个默认的模板参数 std::ratio,表示时钟周期。 ratio的原型:

template <intmax_t _Num, intmax_t _Den = 1>
class ratio

它表示一个时钟周期的秒数。_Num 代表分子,_Den代表分母,它们的比值就是一个时钟周期,可以通过调整分子与分母来表示任意一个时钟周期。如 ratio<1> 表示一个时钟周期为1秒,ratio<3600>表示一个时钟周期为1小时,ratio<1,1000>表示一个时钟周期为1毫秒,ratio<1,2>表示一个时钟周期为0.2秒,ratio<9/7>表示一个时钟周期为9/7秒。 标准库将一些常用的时钟周期做了定义:

typedef ratio<1LL, 1000000000000000000LL> atto;
typedef ratio<1LL,    1000000000000000LL> femto;
typedef ratio<1LL,       1000000000000LL> pico;
typedef ratio<1LL,          1000000000LL> nano;
typedef ratio<1LL,             1000000LL> micro;
typedef ratio<1LL,                1000LL> milli;
typedef ratio<1LL,                 100LL> centi;
typedef ratio<1LL,                  10LL> deci;
typedef ratio<                 10LL, 1LL> deca;
typedef ratio<                100LL, 1LL> hecto;
typedef ratio<               1000LL, 1LL> kilo;
typedef ratio<            1000000LL, 1LL> mega;
typedef ratio<         1000000000LL, 1LL> giga;
typedef ratio<      1000000000000LL, 1LL> tera;
typedef ratio<   1000000000000000LL, 1LL> peta;
typedef ratio<1000000000000000000LL, 1LL> exa;

typedef duration<long long,         nano> nanoseconds;
typedef duration<long long,        micro> microseconds;
typedef duration<long long,        milli> milliseconds;
typedef duration<long long              > seconds;
typedef duration<     long, ratio<  60> > minutes;
typedef duration<     long, ratio<3600> > hours;

在不同的时钟周期中,我们可以使用 chrono_cast 来进行转换。两个 duration还可以进行加减运算:

#include <chrono>
#include <iostream>
using namespace std;

int main(int argc, const char * argv[]) {

    chrono::hours hrs(2);   //两小时
    cout<<hrs.count()<<" hours = ";  //count() 函数获取时钟周期下的时钟数
    chrono::minutes mns = chrono::duration_cast<chrono::minutes>(hrs); //120分钟
    cout<<mns.count()<<" minutes"<<endl;

    chrono::minutes tm = hrs - chrono::minutes(30);  //时长的加减运算
    cout<<hrs.count()<<" hours - 30 minutes = " <<tm.count()<<" minutes = ";

    typedef chrono::duration<double,ratio<3600,1>> hs;  //时长的类型转换。注意 duration 的 _Rep 参数类型。
    hs c = chrono::duration_cast<hs>(tm);
    cout<<c.count()<<" hours"<<endl;


    return 0;
}

//#  输出 #:
// 2 hours = 120 minutes
// 2 hours - 30 minutes = 90 minutes = 1.5 hours

继续阅读

C++11/14 新特性(for循环)

2016/12/05

在C++ 中,遍历一个容器的方法一般是这样的:

#include <iostream>
#include <vector>
int main(void)
{
    std::vector<int> vec;
    //do sth
    std::vector<int>::iterator it = vec.begin();
    for(; it != vec.end(); ++it)
    {
        std::cout<< *it <<std::endl;
    }
    return 0;
}

对STL比较熟悉的程序员肯定还知道在 <algorithm> 中有一个 for_each 算法,可以用来完成上述功能:

int main(void)
{
    std::vector<int> vec;
    //do sth
    std::for_each(vec.begin(), vec.end(), [](auto val){std::cout<<val<<std::endl;});
    return 0;
}

这里借助了 auto 关键字和 lambda 表达式简化了操作。 std::for_each 比起前面 for 循环,最大的好处是不再需要关注迭代器的概念,而只需要关心容器中的元素类型即可。 但这两种方法,都必须显式的给出容器有开头和结尾(begin,end).这是因为上面的两种方法都不是基于范围(range)来设计的. 范围的概念在很多高级语言中都有涉及。例如下面这段 python 代码:

arr = [1,2,3,4,5]
for n in arr:
    print n

在这种循环中,不再需要关心容器的两端,循环会自动以容器的范围进行展开。而且这种语法可以清楚地表明它的意义。使用这种方式进行循环无疑会使编码和维护更加简便。 现在,c++11 可有了基于范围的 for 循环

继续阅读

Coding , 8,600