MySQL插入Emoji提示 HY000 1366 Incorrect string value 错误

==本文前提:mysql Ver 14.14 Distrib 5.7.14, for Linux (x86_64) using EditLine wrapper==

  • 问题描述:在获取微信粉丝信息入库时线上环境产生了错误,而在本地开发时是成功的,线上环境提示nickname字段设置了错误字符串值,如下图:
    MySQL错误提示
  1. 出现这个问题,我明白是有用户的nickname里面含有Emoji导致的.

  2. 经过查找资料,我知晓在Emoji字符有一个特殊的地方,在存储时,需要用到4个字节。MySQL中常用的utf8字符集的utf8_general_ci这个collate最大只支持3个字节。所以为了支持能够存储Emoji,需要修改为utf8mb4字符集;于是用到了这篇文章里提到的方法,不管是修改/etc/my.cnf文件配置,还是在MySQL Client里修改全局配置,错误仍然出现.

    在查询相关资料时,还知道了一个信息,my.cnf中的default_character_set设置只影响mysql命令连接服务器时的连接字符集,不会对使用libmysqlclient库的应用程序产生任何作用,就是说会影响客户端链接时命令执行时的字符集,不影响我使用php脚本里的使用!鸟哥相关文章

  3. 后来我意识到一个问题,会不会是MySQL的sql_mode造成的问题,之前在做数据库迁移也出现过各种不兼容的问题.
    于是,我查看了一下线上MySQL的sql_mode的设置,因为sql_mode是有默认值的,所以在/etc/my.cnf里并没有显性写出,所以只能使用命令select @@sql_mode;在MySQL的客户端里查看,线上环境如下:
    默认的sql_mode

  4. 本地环境的如下:
    本地的sql_mode

  5. 修改/etc/my.conf文件,在mysqld部分增加sql_mode的配置,再次运行程序后成功,修改配置后内容如下图:
    修改后内容

  6. 去MySQL官网查看了5.7版本的Server SQL Modes模块这部分的文档,有这么一句话:

    The default SQL mode in MySQL 5.7 includes these modes: ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE, ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER, and NO_ENGINE_SUBSTITUTION.

大意是,如果没有覆盖配置,那么你的5.7版本的MySQL的SQL mode默认配置就会是上面提到的那些,见3图的默认信息。在MySQL官网有对应每部分详细的说明,我怀疑是STRICT_TRANS_TABLES模式造成插入不成功,我没有测试,最后我只保留了NO_ENGINE_SUBSTITUTION.

NO_ENGINE_SUBSTITUTION:顾名思义,没有引擎时替换。详细来说就是,在创建或修改表时,当指定的数据表引擎不可用或是未编译时,会替换为MySQL默认设置的存储引擎.
> 1. 当此项未设置时,创建表和修改表在引擎不可用时它们的表现也不同。创建表时,如果引擎不可用,MySQL会选择默认的存储引擎,并产生一个警告,但表会创建成功;在修改表结构时,如果引擎不可用,也会产生警告,不同的是修改不会生效.
> 2. 当此项设置时,如果设置的存储引擎不可用,会产生一个错误,并且新建或是修改表都不会成功.

php-fpm里进程管理配置介绍

  • 强调一个观点,给自己的备忘录:
    > 关于开发时使用到的相关软件(php,nginx,php-fpm,mysql等)的使用信息,最好的方式是查看他们的文档和各自的配置信息.
当然没有一定的知识,可能看了也会云里雾里,在此给出自己对php-fpm配置文件的一些理解.
  • 首先,需要知道php-fpm配置文件所在的路径,使用命令ps -ef | grep php,如下图:
    本机
    远程服务器
  1. 可以看到在两台机器上,配置文件的路径不一样,上图的路径是/etc/php/5.6/fpm/php-fpm.conf,下图的路径是/etc/php-fpm.conf,而且php-fpm启动的进程数也不一样,master进程数都为1,worker进程数上图为3个,下图为5个.
  2. 配置文件路径不一样是因为软件安装时软件包里指定的路径不一样,但为什么启动的进程数也不一样呢?这就要看配置文件的详细内容了.
  • 打开php-fpm.conf,内容如下,详细信息请看注释:
;;;;;;;;;;;;;;;;;;;;;
; FPM Configuration ;
;;;;;;;;;;;;;;;;;;;;;

; All relative paths in this configuration file are relative to PHP's install
; prefix (/usr). This prefix can be dynamically changed by using the
; '-p' argument from the command line.

; Include one or more files. If glob(3) exists, it is used to include a bunch of
; files from a glob(3) pattern. This directive can be used everywhere in the
; file.
; Relative path can also be used. They will be prefixed by:
; - the global prefix if it's been set (-p argument)
; - /usr otherwise
; 可以看到,在文件的开始就又加载了另外一个目录下以 .conf 结尾的配置,关于进程运行方式和数量的配置就在这个目录下.
include=/etc/php/5.6/fpm/pool.d/*.conf

........ 以下内容省略,因为此次内容是关于php-fpm进程相关的配置,故只截取了部分内容 ........
  • 打开对应目录/etc/php/5.6/fpm/pool.d/,可以看到www.conf文件,打开文件,可以看到内容有很多,我只截取了与进程数相关的内容,大概从74行开始,不同机器,不同版本php-fpm,文件内容应该大同小异,详情请看注释
; Choose how the process manager will control the number of child processes.
; 注:以何种方式运行php-fpm,进而管理子进程数量,这里列出了三种可选方式:
; 静态模式(static),动态模式(dynamic),按需加载模式(ondemand).

; Possible Values:
; static - a fixed number (pm.max_children) of child processes;
; 注:静态模式,这种模式下,进程数是固定的,固定的个数就是参数 max_children 所设置的个数,此个数不可以动态调整,对于个人本地开发或是小站来说,此模式可以节约资源.

; dynamic - the number of child processes are set dynamically based on the
; following directives. With this process management, there will be
; always at least 1 children.
; 注:动态模式,这种模式下,进程数是会动态改变的,但最少必须保留一个进程,它的启始进程数和动态改变最大进程数受以下参数控制.
; pm.max_children - the maximum number of children that can
; be alive at the same time.
; 最大进程数:动态增加时,进程数的最大值,即使 php-fpm 进程数被使用完要报错了,进程数的总量也不会超过这个值的限定.
;
; pm.start_servers - the number of children created on startup.
; 启始进程数:望文生义即可.

; pm.min_spare_servers - the minimum number of children in 'idle'
; state (waiting to process). If the number
; of 'idle' processes is less than this
; number then some children will be created.
; 最少保留进程数:望文生义即可,需要注意的时,即使没有请求需要处理,空闲的进程数少于这个设定值时,php-fpm的进程数还是会增加,这样就会浪费机器资源,要根据机器实际情况来调整.
;
; pm.max_spare_servers - the maximum number of children in 'idle'
; state (waiting to process). If the number
; of 'idle' processes is greater than this
; number then some children will be killed.
; 最大保留进程数:望文生义即可,需要注意的时,在没有请求需要处理,空闲的进程数大于这个设定值时,php-fpm的进程数会被kill掉.

; ondemand - no children are created at startup. Children will be forked when
; new requests will connect. The following parameter are used:
; 注:按需加载模式,服务启动时没有进程启动,但在请求到来时,会自动fork进程,这种模式下,进程数是会动态改变的,但最少必须保留一个进程,它的启始进程数和动态改变最大进程数受以下参数控制.
;
; pm.max_children - the maximum number of children that
; can be alive at the same time.
; 最大进程数:同上.
;
; pm.process_idle_timeout - The number of seconds after which
; an idle process will be killed.
; 进程空闲保留时间(秒):当进程空闲时,如果在设置的时间内没有被使用将会被kill掉.
; Note: This value is mandatory.
; 注:此参数不能缺省.
pm = dynamic

; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI. The below defaults are based on a server without much resources. Don't
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
; Note: This value is mandatory.
; 注:非缺省值,含义见上.
pm.max_children = 5

; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
; 注:只能作用于动态模式,以动态模式运行时可为缺省值,缺省值为:最少保留进程数 + (最大保留进程数 - 最少保留进程数) / 2. 含义见上.
; 测试过,如果这个值随便填写重启php-fpm报错,但根据这个公式且不超过max_children,则没有问题,更详细需要进一步验证.
pm.start_servers = 2

; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
; 注:只能作用于动态模式,以动态模式运行时为非缺省值,含义见上.
pm.min_spare_servers = 1

; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
; 注:只能作用于动态模式,以动态模式运行时为非缺省值,含义见上.
pm.max_spare_servers = 3

; The number of seconds after which an idle process will be killed.
; Note: Used only when pm is set to 'ondemand'
; Default Value: 10s
; 注:只能作用于按需加载模式,缺省值为10s.
;pm.process_idle_timeout = 10s;
  • 总结:
  1. 以何种方式运行php-fpm,进而管理子进程数量,这里列出了三种可选方式:
    静态模式(static),动态模式(dynamic),按需加载模式(ondemand).

  2. 不同模式有不同参数控制进程数.

懂了配置参数的含义,就明白在不同的情况下要选择什么运行模式,以及为什么在查看进程数不同的机器上会有所不同了.

php-src源码目录说明

下面简单介绍一下PHP源码的目录结构。

  • 根目录: / 这个目录包含的东西比较多,主要包含一些说明文件以及设计方案。 其实项目中的这些README文件是非常值得阅读的例如:
    • /README.PHP4-TO-PHP5-THIN-CHANGES 这个文件就详细列举了PHP4和PHP5的一些差异。
    • 还有有一个比较重要的文件/CODING_STANDARDS,如果要想写PHP扩展的话,这个文件一定要阅读一下, 不管你个人的代码风格是什么样,怎么样使用缩进和花括号,既然来到了这样一个团体里就应该去适应这样的规范,这样在阅读代码或者别人阅读你的 代码是都会更轻松。
  • build 顾名思义,这里主要放置一些和源码编译相关的一些文件,比如开始构建之前的buildconf脚本等文件,还有一些检查环境的脚本等。
  • ext 官方扩展目录,包括了绝大多数PHP的函数的定义和实现,如array系列,pdo系列,spl系列等函数的实现,都在这个目录中。个人写的扩展在测试时也可以放到这个目录,方便测试和调试。
  • main 这里存放的就是PHP最为核心的文件了,主要实现PHP的基本设施,这里和Zend引擎不一样,Zend引擎主要实现语言最核心的语言运行环境。
  • Zend Zend引擎的实现目录,比如脚本的词法语法解析,opcode的执行以及扩展机制的实现等等。
  • pear “PHP 扩展与应用仓库”,包含PEAR的核心文件。
  • sapi 包含了各种服务器抽象层的代码,例如apache的mod_php,cgi,fastcgi以及fpm等等接口。
  • TSRM PHP的线程安全是构建在TSRM库之上的,PHP实现中常见的*G宏通常是对TSRM的封装,TSRM(Thread Safe Resource Manager)线程安全资源管理器。
  • tests PHP的测试脚本集合,包含PHP各项功能的测试文件
  • win32 这个目录主要包括Windows平台相关的一些实现,比如sokcet的实现在Windows下和*Nix平台就不太一样,同时也包括了Windows下编译PHP相关的脚本。

PHP的测试比较有意思,它使用PHP来测试PHP,测试php脚本在/run-tests.php,这个脚本读取tests目录中phpt文件。 读者可以打开这些看看,php定义了一套简单的规则来测试,例如以下的这个测试脚本/tests/basic/001.phpt:

--TEST--
Trivial "Hello World" test
--FILE--
<?php echo "Hello World"?>
--EXPECT--
Hello World

这段测试脚本很容易看懂,执行–FILE–下面的PHP文件,如果最终的输出是–EXPECT–所期望的结果则表示这个测试通过, 可能会有读者会想,如果测试的脚本不小心触发Fatal Error,或者抛出未被捕获的异常了,因为如果在同一个进程中执行, 测试就会停止,后面的测试也将无法执行,php中有很多将脚本隔离的方法比如: system(),exec()等函数,这样可以使用主测试进程服务调度被测脚本和检测测试结果,通过这些外部调用执行测试。 php测试使用了proc_open()函数, 这样就可以保证测试脚本和被测试脚本之间能隔离开。phpt文件的编写详细信息可参考 附录E phpt文件的编写。 如果你真的那么感兴趣,那么研究下$PHP_SRC/run-tests.php脚本的实现也是不错的选择。这个测试框架刚开始 由PHP的发明者Rasmus Lerdorf编写,后来进行了很多的改进。后面可能会引入并行测试的支持。

自我剖析

今天发生了一件事,和同事对某个功能的封装发生了一场小争论,他认为应该把这个功能封装成两个方法,以下是他的观点:

代码一旦出现岔路型变量,证明封装有问题(如这个里的 is_group_by);

对于使用者,这样的封装并没有透明化 “是否用 group by 逻辑” 对于之后的维护者,这类的方法势必两个分支会有不共用的逻辑,最终会导致一个大方法出现;

从两个角度考虑方便与否,一个是使用者,对于用参数还是用两个方法,使用者都需要知晓,使用者成本一样。对于可读性,多一个分岔路标变量会影响代码可读性,对于可维护性而言,分岔逻辑势必意味着大函数的出现;

  1. 永远不要断言未来,变的时候会去考虑改吗,这是一个明日复明日的问题,终有一个明日改不动了;
  2. is_group_by 可读性就好了吗.
而我的观点很简单:
  1. 不想新增新方法;
  2. 其它已经使用的地方不需要有改动.
在这记录一下我内心的想法:
  • 其实我是很赞同他的观点的,因为我内心是明白什么是好的设计,什么是差的设计。
  • 开始我也是那样想的,但由于要改动的地方太多,太麻烦,所以选择了简单的方式。
  • 以下这个想法不排除有推卸责任的嫌疑:你不能既让我在短时间里去实现业务逻辑,还要求一个很高的设计(搞什么,我现在写这篇稿子的时候,也开始觉得有推卸责任的嫌疑了)。
由今天的事引申
  • 不要在意别人在你背后说的话, 是真的不在意。

  • 除了上一条外,好的建议就要听。

mysql客户端emma中文乱码问题的解决

emma默认用apt-get 安装的话,emma是不支持中文的,配置文件或直接修改emma程序源文件(python)。

apt-get安装emma

sudo apt-get install emma

ubuntu的apt-get 安装emma是在/usr/share/emma目录下面。

cd /usr/share/emma/emmalib

sudo vim init.py

找到

“db_encoding”: “latin1”

改为

“db_encoding”: “utf8”

保存退出。

如果你已经安装完毕并且运行过emma,程序就会创建 ~/.emma/emmarc文件,保存配置。所以可以更改这里的配置文件,或者像下面直接修改emma的python源文件。
vim ~/.emma/emmarc
找到
db_encoding=latin1
改为
db_encoding=utf8
重新运行emma,此时发现还是乱码,在执行所有的sql语句之前加入这条sql语句,
set names utf8
但每次新用户都要改配置文件,以及执行新sql前都加这个语句,岂不是很费力,直接修改emma的源文件,来实现,新创建的emmrc配置文件就是utf8,和当选择数据库时,自动的执行“set names utf8” 语句。

以后新创建的配置文件默认就会是utf8的解码了,我想在连接数据之后就执行 “set names utf8” 语句,所以

sudo vim /usr/share/emma/emmalib/mysql_host.py

但是我下边这一步没执行成功,写完之后,emma 就没发启动了,

跳到155行左右的_use_db(self, name, do_query=True)函数哪里,改成如下

def _use_db(self, name, do_query=True):

if self.current_db and name == self.current_db.name: return

if do_query:

self.query(“use %s” % name, False)

self.query(“set names utf8”,  False)

try:

self.current_db = self.databases[name]

except KeyError:

print “Warning: used an unknown database %r! please refresh host!/n%s” % (name, “”.join(traceback.format_stack()))

ubuntu个人机使用到的一些软件

个人目前使用ubuntu,主要是用来进行web开发,在安装完ubuntu后,可以看到默认是有浏览器firefox,办公套件LiBreOffice,电邮客户端Thunderbird Mail,好了接下来说说我为了个人需要安装一些软件,仅供参考。
  • 通迅相关
  1. QQ International 这个不是官方发布的客户端,具体开发者我也不知道是谁,但我在使用ubuntu 13.x 的时候就使用的这个客户端,到目前使用的16.04LTS版都没有出现过问题,应该具备QQ2013的所有功能,办公使用足够了。如果有人需要这个deb包可以留言回复,我会发送文件到你邮箱并协助你安装。注意:需要先安装Wine(sudo apt install wine).

  2. Electronic WeChat 微信客户端,这个也不是腾讯官方发布的客户端,是一个开源项目。具体安装方法在github上面写的非常清楚,当然,如果你有不明白的地方,也可以回复我,我会尽我所能帮助你。


  • 输入法
  1. fcitx ubuntu有自带的ibus,不过我更喜欢使用fcitx,使用它的五笔拼音,打字嗖嗖的。

  • 浏览器
  1. google-chrome-stable安装文档

  2. chromium chromium是开发版,各种新功能都会有,但ubuntu的包却是很落后,刚开始我用的是chromium,后台更换为了chrome。


  • 音乐
  1. 厉害了word我的网易,网易云音乐有专门ubuntu对应版本的包,一般人我不告诉他,16.04LTS亲测能用,一直使用的网易云音乐

  • 虚拟机
  1. VirtualBox 不管是为了在Linux上使用Windows(Linuxer的痛)还是为了统一开发环境使用vagrant,都需要安装VirtualBox。

  • 数据库客户端
  1. emma 最开始我一直使用的emma,界面简洁,查看方便,能使用SQL语句,相对功能会少一些,且apt安装后有个小问题,不支持中文显示,解决方法

  2. navicat for mysql navicat对比其它Linux下Mysql客户端确实强大太多了,常用的各种功能自不在话下,还支持视图,函数,事件等等,下载解压后进入目录,运行脚本启动即可,但navicat也有个小问题,它不是免费的,只有15天试用期,不过你可以到目录.navicat下把user.reg删掉(cd ~/.navicat/),然后重启,你就又有15天的试用期了。


  • markdown编辑器
  1. Haroopad 界面美观,清晰,支持实时预览等很多功能,还有我最舒适的vim操作模式,我只使用了它的markdown语法编辑功能,目前只能导出HTML格式文件,下载后点击安装即可,此文就是通过这个软件编写的。

  2. Atom A hackable text editor for the 21st Century,Atom编辑器功能更强大,放到这个模块下面,其实是不合理的,因为它不仅仅是一个markdown编辑器,更是一个功能强大的文本编辑器,由于我使用的是(G)vim,所以只是把Atom当作写markdown文档的工具在使用,使用Atom的原因是家里电脑使用的是ArchLinux,ArchLinux上没有Haroopda的包,不得已找到了Atom,才发现它也非常强大,有超多第三方插件可供选择使用,除了自带插件,我搜索下载了支持vim的vim-mode,md文件转换为pdf的markdown-pdf,此次更新文件使用Atom修改。


  • 图片编辑工具
  1. GIMP Image Editor 是一个很强大的图片编辑工具,据说是Linux下的PhotoShop,当时我需要制作一个自己博客的logo,此网站的logo就是使用这个软件制作的,最后就找到了这个软件,目前使用了此软件很小一部分功能。

  2. gnome-screeshot 顺带说一下gnome自带的截图工具,我自己在系统设置,键盘,快捷方式里设置了它的快捷键ctrl + alt + a对应gnome-screenshot -a,此后截图就非常方便了。


  • 开发相关
  1. PHP7 Nginx mariaDB就不用说了.

  2. vim-gtk 我之前使用的vim-gnome编辑器,后来在每次关闭vim时,都会提示一个错误,在搜索这个问题时,看到Stack Overflow上别人推荐使用vim-gtk,使用下来一点不差.

  3. Composer PHP 的一个依赖管理工具,安装文档.


  • 科学上网
  1. Shadowsocks科学上网工具,结合浏览器插件SwitchyOmega或是终端代理工具ProxyChains.注意:直接apt install shadowsocks安装的可能不支持rc-md5,可先安装pip(sudo apt install python-pip),然后用pip安装(pip install shadowsocks).

  • 系统设置
  1. 使用monaco字体,参考github一篇文档,clone或下载文件,请仔细阅读README.md,最后找到其中为Ubuntu定制的安装脚本。运行命令./install-font-ubuntu.sh https://github.com/todylu/monaco.ttf/blob/master/monaco.ttf?raw=true,文档里提供的地址已失效。

  2. Unity Tweak Tool设置系统工具,可以在Ubuntu应用商店搜索安装,安装后可以使用第一步里安装好的monaco字体.


  • 其它相关问题
  1. 配置thunderbird,qq企业邮箱服务时要补全为exmali.qq.com

  2. 字的问题,一些字体显示不正确。
    /ect/honts/conf.d/64-language-selector-prefer.conf 有以下内容:

<alias>
      <family>sans-serif</family>
      <prefer>
         <family>Noto Sans CJK JP</family>
         <family>Noto Sans CJK SC</family>
         <family>Noto Sans CJK TC</family>
      </prefer>
   </alias>
   <alias>
      <family>monospace</family>
      <prefer>
         <family>Noto Sans Mono CJK JP</family>
         <family>Noto Sans Mono CJK SC</family>
         <family>Noto Sans Mono CJK TC</family>
      </prefer>
   </alias>

你要做的就是把 JP 换到最后面,然后重启电脑。

PHP魔术常量的示例

php的魔术常量有8个

私以为这个顺序是比较方便记忆的,从整体到局部,分别是:
__DIR__       文件所在目录的绝对路径

__FILE__     文件所在绝对路径

__LINE__    文件所在绝对路径

__NAMESPACE__    当前命名空间名称

__CLASS__    当前类名称

__TRAIT__     当前Trait名称

__MEHTOD__   当前方法名称

__FUNCTION__    当前函数名称

有一点是需要注意的就是trait的优先级,从基类继承的成员会被 trait 插入的成员所覆盖。优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。当前类的方法又会覆盖trait的方法。
当有使用到Trait时,且没有被当前类里的方法覆盖时__METHOD__输出的是trait里的方法;

否则,__METHOD__输出的是当前类里的方法。

以下是代码。

<?php
namespace bobo;

trait Lianbo {
    public function test()
    {
        echo 'hello world'.PHP_EOL;

        // /var/www/html/test
        echo __DIR__.PHP_EOL;

        // /var/www/html/test/magic_const.php
        echo __FILE__.PHP_EOL;

        // 16
        echo __LINE__.PHP_EOL;

        // bobo echo 
        __NAMESPACE__.PHP_EOL;

        // bobo\Lianbo 
        echo __TRAIT__.PHP_EOL;
        // bobo\Bobo
        echo __CLASS__.PHP_EOL;

        // bobo\Lianbo::test
        echo __METHOD__.PHP_EOL;

        // test
        echo __FUNCTION__.PHP_EOL;
    }
}

class Bobo {
    use Lianbo;

    //public function test() {
        //echo __DIR__.PHP_EOL;
        //echo __FILE__.PHP_EOL;
        //echo __LINE__.PHP_EOL;
        //echo __NAMESPACE__.PHP_EOL;
        //echo __CLASS__.PHP_EOL;
        //echo __TRAIT__.PHP_EOL;
        //echo __METHOD__.PHP_EOL;
        //echo __FUNCTION__.PHP_EOL;
    //}
}

$obj = new Bobo;
$obj->test();

图片第一张是class Bobo里test()方法注释时的结果,第二张是注释打开时的结果。

一次http请求的过程

  • ##### 用户访问

首先会输入网站URL,例如:http://blog.blianb.com,这个时候DNS( Domain Name System):“域名系统”,会把域名翻译为对应的IP地址。为什么会有这一步呢?你可以先简单的理解为手机里保存的手机号与对应姓名,QQ号与备注的关系,目的是为了辨识和使用。

  1. 输入URL回车后,计算机会先查找浏览器的缓存,如果浏览器没有缓存这个URL对应的IP地址,或者缓存已经过期,那么计算机会接着查找操作系统本地DNS解析器的缓存。

    注:我们怎么查看Chrome自身的缓存?
    > 可以使用 chrome://net-internals/#dns 来进行查看,如下图,标红为过期:
    浏览器缓存

  2. 计算机检查本地hosts文件里是否含有这个映射关系
    Windows的hosts文件一般在C:\Windows\System32\drivers\etc\hosts
    Linux的hosts文件一般在/etc/hosts里

    注:Linux文件路径和内容如下图:
    host文件路径
    host文件内容
    btw,一般做网站开发的都会修改这个文件,配合本地的web server实现虚拟主机,方便在本地测试,127.0.0.1代表请求本地的服务

  3. 查找网络设置里的DNS服务器(区域)

  4. 检查此DNS服务器里是否含有这个IP地址映射关系

  5. 最后转至根DNS服务器查询,以下是DNS服务器的一些相关资料:

名称类型 说明 示例
根域 DNS域名中使用时,规定由尾部句点(.)来指定名称位于根或更高级别的域层次结构 单个句点(.)或句点用于末尾的名称
顶级域 用来指示某个国家/地区或组织使用的名称的类型名称 .com
第二层域 个人或组织在Internet上使用的注册名称 blianb.com
子域 已注册的二级域名派生的域名,通俗的讲就是网站名 www.blianb.com
主机名 通常情况下,DNS的域名的最左侧的标签标识网络上的特定计算机,如h1 blog.blianb.com
DNS域名称 组织类型
com 商业公司
edu 教育机构
net 网络公司
gov 非军事政府机构
Mil 军事政府机构
xx 国家/地区代码(cn代表中国)
  1. DNS工作简图:
    DNS工作简图

  • ##### 发起TCP三次请求
    > 拿到域名对应的IP地址之后,User-Agent(一般是指浏览器)会以一个随机端口(1024 < 端口 < 65535)向服务器的web server(常用的有apache,nginx等)80端口发起TCP的连接请求。这个连接请求(原始的http请求经过TCP/IP4层模型的层层封包)到达服务器端后(这中间通过各种路由设备,局域网内除外),进入到网卡,然后是进入到内核的TCP/IP协议栈(用于识别该连接请求,解封包,一层一层的剥开),还有可能要经过Netfilter防火墙(属于内核的模块)的过滤,最终到达web server(本文就以Nginx为例),最终建立了TCP/IP的连接。
  1. 使用子网掩码判断客户机和服务器是否在一个子网络

  2. 在同一个子网络,则使用以太网进行广播发送数据包

  3. 不在同一个子网络,则通过网关转发

涉及的相关资料:
OSI模型七层协议
tcp的三次握手,建立连接
TCP/IP模型是一系列网络协议的总称,TCP/IP模型一共包括几百种协议,对互联网上交换信息的各个方面都做了规定


  • ##### web server(nginx)
要知道http请求到达web server之后,web server是怎么工作的,就要先知道以下几个概念:
  1. CGI:CGI 是 Web Server 与后台语言交互的协议;

  2. FPM (FastCGI Process Manager),它是 FastCGI 的实现,任何实现了 FastCGI 协议的 Web Server 都能够与之通信;

  3. FPM 是一个 PHP 进程管理器,包含masterworker两种进程:master进程只有一个,负责监听端口,接收来自Web Server的请求,而worker进程则一般有多个 (具体数量根据实际需要配置),每个进程内部都嵌入了一个 PHP 解释器,是 PHP 代码真正执行的地方,下图是我本机上 fpm 的进程情况,1个master进程,多个worker进程(本地有3个,云服务器上有5个worker);
    本机:
    本机

运程服务器:
远程服务器

  1. 关于php-fpm worker进程的配置说明,详见php-fpm里进程管理配置介绍;

  • ##### web server按端口监听到请求后,会分配给worker进程,调用相关PHP脚本进行具体的处理。

  • ##### php-fpm执行php文件
    涉及到解释器的执行过程
    通过socket和数据库或是缓存等交互

  • ##### 返回结果
    也是通过网络协议

  • ##### 浏览器显示

转:或许你也患上了开发瘫痪症

亲爱的开发者们:你们是否因为自己只精通于三大设备平台的八种编程语言而惴惴不安?又发现一个JavaScript框架是否会让你不寒而栗、愁眉苦脸?你是否曾经因为无法确定哪个云平台最适合而把业余项目一再推迟?

或许你也换上了开发瘫痪症(Developaralysis)。颤抖吧,这个病是治不好的。

如今开发者们可选的技术方案多到令人发指,让人眼花缭乱,透不过气来。过去几年里,我拿着别人给我的酬劳,写过Java、Objective-C、C、C++、Python、Ruby、JavaScript、PHP(对不起,这个也算),用过各种各样的SQL/键值/文件数据存储技术(MySQL、PostgreSQL、MongoDB、BigTable、Redis、Memcached等等)。我是否自我感觉良好?上帝啊,一点也没有。我反而感觉到愧疚,因为我还没有用过Erlang、Clojure、Rust、Go、C#、Scala、Haskell、Julia、Scheme、Swift或者是OCaml。

我就是一名开发瘫痪症患者:软件产业发展太快、任何一个人都无法跟上,从而对我的意识造成了毁灭性打击。

上面提到的几乎任何一种语言,你都会找到无数可选的框架、套件和库——看得头都要爆炸了。如今仅仅是把JavaScript的框架和库的所有排列认真评估一遍就要花上几个月的时间。另外,你知道Ruby语言有多少种gem包吗?有多少种iOS框架吗?有多少种NewSQL或NoSQL的数据库技术吗?更不用说从Hadoop、Spark与Google Dataflow中进行选择了,究竟是用Avro,还是Thrift,还是协议缓冲区,等等,等等……

还好,移动领域已经简化到了Android、iOS两大垄断平台——尽管也隐藏着一些交叉代替方案,比如Xarmarin或者PhoneGap、Sencha这样的跨平台HTML技术——但是确定在哪个平台上部署后端、如何部署又会让你头大。我开发过的各个系统部署在Heroku、Amazon Web Services、Google App Engine、Google Compute Engine以及Parse上面……这让我感觉非常糟糕,因为我对OpenStack、Force.com、Azure、Appfog一无所知,好多AWS服务我也从来没有真正用过,说多了都是泪。

I Am Devloper @iamdevloper

2014年编写简单网站的步骤:

  1. 安装Node

  2. 安装Bower

  3. 选择CSS框架

  4. 选择敏捷的方法

……

  1. 写几行HTML代码

如今的开发者面临着太多的选择,以至于使用的许多工具仅仅是用来管理另一堆工具:比如Bundler、Bower、CocoaPods、Pip等等。这些东西太棒了!我完全离不开它们!别高兴得太早。你开始使用另一堆工具后,等用到一半的时候你真正理解了它们的内容,你开始觉得这样的配置还不够用,你有点想要把它们重写一遍了……也许再找另一个工具代替……

可悲的是,如今开发者可用的语言、工具、框架以及平台的多样性和绝对数量庞大到让人畏惧。当然没人会承认这一点。所有人都想装作精通所有语言的编程大师。然而事实却是,我们都已经深陷于开发瘫痪症之中无法自拔。

即便收集了各种信息做出了最明智的选择,结果往往也适得其反。比如说,在项目开始前,你真的花时间分析了所有的可能,克服了由此产生的学习曲线,结果却被一些用着PHP、Swift这样易于上手的语言,写着表情符号变量名的小屁孩抢了市场先机——

——不过另一方面,如果你选择使用Swift和PHP的话,你就会生活在无休止的恐惧之中,担心一些C#/Haskell程序员高手很快就会做出更好的产品几百你,就像保罗·格拉汉姆(Paul Graham)很多年前用Lisp语言编写Viaweb那样。回忆往昔,他这样写道:

当你选择了技术之后,你必须要忽略其他人在做什么,全心全意思考怎样做到最好……事实上我们确实有一个秘密武器……我们开发软件的速度超出了所有人的想象……我们用一种全是括号的语法奇怪的AI语言编写出了我们的软件。

再说回开发瘫痪症。我们应该选择已经掌握的技术吗?这样我们就能立刻动手开发,无需克服学习曲线,但是需要生活在恐惧之中,担心其他人在以更好、更快、更优雅的方式实现同样的产品,担心到了明年我们的技能就跟不上时代、丧失竞争力了。还是应该选择未知的新技术?因为我们热爱学习,更好的工具使用起来不仅更加有趣,而且能够带来巨大的竞争优势……付出的代价则是大量的时间、精力和认知负荷。

这个问题太难回答了!不存在绝对正确的答案。因此每个月都是开发瘫痪症发作月。我马上会做好五颜六色、闪闪发光的丝带给你们佩戴,只要等我选好用哪种3D打印机、哪种微控制器、哪种LED软件、哪种无人机系统就行了。恐怕要麻烦你们期待很久很久了。

翻译:顾秋实

使用CodeIgniter框架搭建RESTful API服务

我记得这是在很早之前看的,需要翻墙才能看到,为了方便查看,所以给记录到笔记了,转载:

在2011年8月的时候,我写了一篇博客《使用CodeIgniter框架搭建RESTful API服务》,介绍了RESTful的设计概念,以及使用CodeIgniter框架实现RESTful API的方法。转眼两年过去了,REST在这两年里有了很大的改进。我对于前一篇博客中的某些方面不是很满意,所以希望能利用这次机会写一个更加完善的版本。

我的项目基于Phil SturgeonCodeIgniter REST Server,遵循他自己的DBAD协议。Phil的这个项目很棒,干净利落,简单实用,并且极具特色,解决了我自己项目中两个问题:在请求中查询语句的使用,以及复杂的身份验证方法。正如我前面所说,我的项目基于Phil的项目开发,并且只有一个方面不同:我给每个资源分别分配一个控制器,而不是用一个独立庞大的控制器进行统一管理。使用多个独立控制器的好处是维护起来更加简单方便。

完整的项目源码可以在awhitney42/codeigniter-restserver-resources下载。

RESTful

在深入探讨之前,我们先来回顾一下TESTful的概念。REST全名Representational State Transfer,中文可以译为:表现层状态转化,是一种网络服务的架构工具,而不仅仅是一套接口规范。

使用名词而不是动词

你的RESTful接口应该提供访问而不是方法。
所以,这样是不可取的:

1 
2
/createCustomer 
/getCustomer?id=666

应该这样:

1 
2
POST /customers 
GET /customers/666

所有东西都应该有ID

REST的优点之一就是简洁,一定程度上来讲,REST只是一个URI的集合,每个资源都需要一个独一无二的ID作为一个标识。

1 
2
GET /customers/666 
GET /products/4234

使用动词进行操作

在REST里,使用不同的HTTP动词进行增删改查的操作:

动词 操作
POST 新建一个资源
GET 读取一个资源
PUT 更新一个资源
DELETE 删除一个资源

使用这个REST服务的映射表来设计接口,在开发客户端的时候可以轻松上手,不用深入理解接口的含义。这套标准十分适用于REST,符合面向服务架构(SOA)的核心设计思想:服务抽离、松耦合、可复用、可发现、健全性。

通过这个一致的映射关系,客户端也知道哪个动词是幂等(Idempotent)的。幂等是指这个操作可以重复多次,并且每次都会得到相同的结果。参照HTTP规范,GET、PUT和DELETE操作是幂等的,POST操作不是幂等的。这也就是为什么在REST中使用POST来进行添加操作,而使用PUT来进行更新操作。

把东西链接起来

REST中的一个核心概念是HATEOAS(Hypermedia As The Engine Of Application State),即“超媒体即应用状态引擎”。这意味着应该始终使用链接(超媒体)来获取资源信息,然后客户端通过这个链接就可以获取到不同的资源。

所以,相关的资源应当返回一条链接,而不是返回整个资源的内容。
也就是,该这样:

1
2 
3 
4 
5 
6 
7 
8
<officer id="1">  
    <name>James T. Kirk</name>  
    <rank>Captain</rank>  
    <subordinates>  
        <link ref="http://ncc1701/api/officers/2">  
        <link ref="http://ncc1701/api/officers/3">
    </subordinates> 
</officer>   

而不该这样:

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14
<officer id="1">  
    <name>James T. Kirk</name>  
    <rank>Captain</rank>  
    <subordinates>  
        <officer id="2">  
            <name>Spock</name>  
            <rank>Commander</rank>  
        </officer>  
        <officer id="3">  
            <name>Doctor Leonard McCoy</name>  
            <rank>Commander</rank>  
        </officer>  
    </subordinates> 
</officer>

返回资源的链接应该用完整的URI地址而不是相对路径,这要求客户端请求这些资源的当前状态,维持HATEOAS原则。使用完整的URI地址的好处是客户端不需要额外的了解API相关的内容,只需要简单的访问这些链接就可以了。

提供多种资源表现方式

REST中的R代表Representational,即表现层,这意味着REST服务应该提供不同的表现方式,以全面支持不同的客户端请求。在一次HTTP请求中,服务器可以通过指定的表现方式返回资源,REST服务应该使用标准的表现方式,以便客户端之间的信息互通。

比如可以通过如下请求XML格式的数据:

1 
2
GET /customers/666 
Accept: application/xml

或者vcard格式:

1 
2
GET /customers/666 
Accept: application/vcard

或者pdf格式:

1 
2
GET /customers/666 
Accept: application/pdf

或者自定义格式:

1 
2
GET /customers/666 
Accept: application/vnd.mycompany.customer-v1+json

使用状态码作为回复

HTTP的状态码提供了一套标准化方案,用来反馈请求的状态。

含义 解释
200 OK 确认GET、PUT和DELETE操作成功
201 Created 确认POST操作成功
304 Not Modified 用于条件GET访问,告诉客户端资源没有被修改
400 Bad Request 通常用于POST或者PUT请求,表明请求的内容是非法的
401 Unauthorized 需要授权
403 Forbidden 没有访问权限
404 Not Found 服务器上没有资源
405 Method Not Allowed 请求方法不能被用于请求相应的资源
409 Conflict 访问和当前状态存在冲突

CodeIgniter

CodeIgniter是一个流行的MVC框架,很适合用来进行RESTful API开发。控制器(Controller)处理客户端的请求并返回内容,模型(Model)进行增删改查(CRUD)的操作,视图(View)用来处理资源的表现格式。不过在这个例子里,我们没有用模型,而是直接用控制器进行格式处理,这样整个项目更干净更简单。

代码可以直接在Github中获取:codeigniter-restserver-resources

在接下来的例子里,我们用到了REST_Controller这个库,继承自原生的CI_Controller。它可以完成绝大部分繁杂的工作:处理请求、调用模块、格式化内容、返回数据。你的每个资源控制器都应该继承自REST_Controller这个类。

下面我们来看一个例子,我们假设要提供一个Widgets资源的RESTful接口:

1
class Widgets extends REST_Controller

get()

Widgets类中的第一个函数是get(),用来响应HTTP的GET请求。这个函数调用了父类的protected函数_get(),用来获取请求中的参数ID。然后这个函数根据是否有参数ID使用widgets_model调用getWidgets()或者getWidget($id)方法,模型的返回值将会通过父类的response()函数返回,返回的内容包含对应的状态码和符合格式的数据。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17
function get() {
   $id = $this->_get('id');
   if(!$id)  {
      $widgets = $this->widgets_model->getWidgets();
         if($widgets)  
             $this->response($widgets, 200);
             // 200 being the HTTP response code  
         else  
             $this->response(array('error' => 'Couldn't find any widgets!'), 404);
    }  
    $widget = $this->widgets_model->getWidget($id);  
    if($widget)  
        $this->response($widget, 200); 
        // 200 being the HTTP response code  
    else  
        $this->response(array('error' => 'Widget could not be found'), 404); 
}

cURL是一个很好的命令行工具,我们可以用来测试REST服务,返回一个JSON格式的数据:

1
2
$ curl -i -H "Accept: application/json" -X GET 
http://foo.com/index.php/api/widgets

则会返回如下内容:

1 
2 
3 
4 
5
HTTP/1.1 
200 OK 
Status: 200 
Content-Type: application/json  
{"1":{"id":1,"name":"sprocket"},"2":{"id":2,"name":"gear"}}

请求特定的资源也很简单,这次我们去请求ID为2的widget并且通过XML格式返回:

1
2
$ curl -i -H "Accept: application/xml" -X GET 
http://foo.com/index.php/api/widgets/id/2

返回如下内容:

1 
2 
3 
4 
5 
6
HTTP/1.1 
200 OK 
Status: 200 
Content-Type: application/xml  
<?xml version="1.0" encoding="utf-8"?> 
<xml><id>2</id><name>gear</name></xml>

如果请求一个不存在的资源就会返回404的错误码:

1 
2 
3 
4 
5 
6
HTTP/1.1 
404 Not Found 
Status: 404 
Content-Type: application/xml  
<?xml version="1.0" encoding="utf-8"?> 
<xml><error>Widget could not be found</error></xml>

post()

接下来的函数是post(),用来处理创建widget的POST请求。可以通过$this->_post_args获取请求的数据。父类通过Format.php对请求的数据进行处理并把它们放到了$this->_post_args里。接下来post()方法使用widgets_model模型调用createWidgets($data)函数,如果数据非法或者请求冲突,widgets_model模型会抛出异常并且返回异常的内容。如果调用成功,则会调用getWidget($id)函数获取最新的widget,在返回的时候会将返回值和201 (Created)的状态码一起返回。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21
function post() 
{  
    $data = $this->_post_args;  
    try {  
        $id = $this->widgets_model->createWidget($data);
    } catch (Exception $e) {  
        // Here the model can throw exceptions like the following:  
        // * Invalid input data:  
        // throw new Exception('Invalid request data', 400);  
        // * Conflict when attempting to create, like a resubmit:  
        // throw new Exception('Widget already exists', 409)  
        $this->response(array('error' => $e->getMessage()),  $e->getCode());  
    }  
    if ($id) {  
        $widget = $this->widgets_model->getWidget($id);  
        $this->response($widget, 201); 
        // 201 is the HTTP response code  
    } else  
        $this->response(array('error' => 'Widget could not be created'),  404); 
    }
}

为了测试通过POST请求新建资源的操作,我们需要把我们的cURL包裹在PHP代码里,创建一个rest_client.php文件:

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20
print "n-----TESTING REST POST-----n"; 
test_post(); 
function test_post() 
{  
    $data = array("name" => "bolt");  
    $data_string = json_encode($data);  
    $ch = curl_init('http://foo.com/index.php/api/widgets');  
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");  
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);  
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(  
       'Content-Type: application/json',  'Content-Length: ' . strlen($data_string))  
    );  
    $result = curl_exec($ch);  
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);  
    $contenttype = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);  
    print "Status: $httpcode" . "n";  
    print "Content-Type: $contenttype" . "n";  
    print "n" . $result . "n"; 
}

在命令行中通过PHP命令执行:

1
$ php rest_client.php

返回内容如下:

1 
2 
3 
4 
5 
6
-----TESTING REST POST----- 
Status: 201 
Content-Type: application/xml  
<?xml version="1.0" encoding="utf-8"?> 

<xml><id>3</id><name>bolt</name></xml>

put()

接下来的方法是put(),用来处理PUT请求,更新已经存在的资源数据。处理过程和POST的处理十分相似,最大的区别就在于使用$this->_put_args而不是$this->_post_args,以及返回200而不是201。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20
21
22
public function put() {  
    $data = $this->_put_args;  
    try {  
        //$id = $this->widgets_model->updateWidget($data);  
        $id = $data['id']; 
        // test code  
        //throw new Exception('Invalid request data', 400); 
        // test code  
    } catch (Exception $e) {  
        // Here the model can throw exceptions like the following:  
        // * For invalid input data: new Exception('Invalid request data', 400)  
        // * For a conflict when attempting to create, like a resubmit: new Exception('Widget already exists', 409)  
        $this->response(array('error' => $e->getMessage()), $e->getCode());  
    }  
    if ($id) {  
        $widget = array('id' => $data['id'], 'name' => $data['name']); 
        // test code  //$widget = $this->widgets_model->getWidget($id);  
        $this->response($widget, 200); 
        // 200 being the HTTP response code  
    } else  
        $this->response(array('error' => 'Widget could not be found'), 404); 
}

为了测试UPDATE更新资源的功能,我们依旧使用PHP进行cURL的操作,大多数的网络服务器默认没有开启PUT和DELETE,我们可以在header中使用X-HTTP-Method-Override,通过POST来发送PUT请求。这样的话,服务器会把它当做一个POST请求,而REST服务器会把它作为PUT操作处理。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22
print "n-----TESTING REST PUT-----n"; 
test_put();  
function test_put() 
{  
    $data = array("id" => "3", "name" => "nut");  
    $data_string = json_encode($data);  
    $ch = curl_init('http://foo.com/index.php/api/widgets');  
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");  
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);  
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'X-HTTP-Method-Override: PUT',  
        'Content-Type: application/json',  
        'Content-Length: ' . strlen($data_string))  
    );      
    $result = curl_exec($ch);  
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);  
    $contenttype = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);  
    print "Status: $httpcode" . "n";  
    print "Content-Type: $contenttype" . "n";  
    print "n" . $result . "n"; 
}

同样使用PHP命令测试:

1
$ php rest_client.php

返回结果:

1 
2 
3 
4 
5 
6
-----TESTING REST PUT----- 

Status: 200 
Content-Type: application/xml  
<?xml version="1.0" encoding="utf-8"?> 
<xml><id>3</id><name>nut</name></xml>

delete()

最后的函数就是delete了,请求中必须包含ID的参数否则就会返回400(Bad Request)的状态码,因为我们不希望用户删除所有的资源内容。如果ID没有对应的资源则会返回404(Not Found) 错误码。如果对应的widget存在,则会尝试调用deleteWidget($id)删除对应资源。操作中的所有异常都会被捕获到并且返回。如果删除成功,返回200(Success)状态码。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23
function delete() 
{  
    $id = $this->_get('id');  
    if(!$id)  
    {  
        $this->response(array('error' =>  'An ID must be supplied to delete a widget'), 400);
    }  
    //$widget = $this->widgets_model->getWidget($id);  
    $widget = @$widgets[$id]; 
    // test code  
    if($widget) {  
        try {  
            $this->widgets_model->deleteWidget($id);
       } catch (Exception $e) {  
           // Here the model can throw exceptions like the following:  
           // * Client is not authorized: new Exception('Forbidden', 403)  
           $this->response(array('error' => $e->getMessage()),  $e->getCode());
       }  
       $this->response($widget, 200); 
       // 200 being the HTTP response code  
    } else  
        $this->response(array('error' => 'Widget could not be found'), 404); 
}

我们可以用命令行工具cURL进行测试,参数ID像GET一样放在URL地址中,和PUT相同,我们通过X-HTTP-Method-Override使服务器把请求当做POST处理,REST服务则会把这次请求当做DELETE操作处理。

1
2
$ curl -i -H "Accept: application/xml" -H "X-HTTP-Method-Override: DELETE" 
-X POST http://foo.com/index.php/api/widgets/id/1

返回200 (Success)说明id为1的资源已经被成功删除:

1 
2 
3 
4 
5 
6
HTTP/1.1 
200 OK 
Status: 200 
Content-Type: application/xml  
<?xml version="1.0" encoding="utf-8"?> 
<xml><id>1</id><name>sprocket</name></xml>

如果ID非法,则会返回404(Not Found)错误码,如果没有删除的权限,则会返回403 (Forbidden) 状态码。

Authentication

正如前面提到的,Phil的REST框架和我的原来的设计相比,在用户权限认证上有了很大的改进, 提供basicdigest两种认证方式。还有很多其他特性,比如可以用LDAP字典将授权融为一体,具体可以在config/rest.php中设置。

除了传统的认证方式,你还可以使用API keys或者IP地址的白名单进行用户权限管理。

DTO

在原先的设计中,我使用DTO(Data Transfer Object)在不同格式之间传输数据,Phil使用Format类来解决这个问题,从Widget的例子中我们可以看到,使用数据是多么的方便,只需要$this->_post_args或者$this->response()就可以解决问题。当然,这并不意味着DTO不好用,但是在很多场合下它会显得十分复杂和庞大。
在我原来的项目里,我对客户端需要使用和服务器端同样的DTO库来传输数据很不满意,因为它违反了KISS的原则。

总结

REST目前已经是比较成熟的网络服务的框架模型方案,是API产品目录网站programmableweb的基石,是各式各样应用和服务器的数据传输的基础,同时对于SOA来说也是十分重要,促进网络端和移动端的接口技术日趋成熟。

正如前面所看到的,用CodeIgniter框架实现RESTful接口十分简单,可以下载我的项目源码awhitney42/codeigniter-restserver-resources,从现在就开始开发吧!

后记

REST是一种遵从传统设计模式的架构风格,在过去的几年中一直都在改进,并且会一直坚持下去。所以如果对于REST或者是本文有什么意见,或者对于其中的概念有什么困惑,请在文章后面留言。