php发送邮件

0. 概述

php manual中关于mail的介绍很简单,

@param to 电子邮件收件人或收件人列表
@param subjct 电子邮件的主题 也就是收件人或收件人列表
@param message 所要发送的消息 
@return true if the mail was successfully accepted fro delivery, FALSE otherwise
bool mail ( string $to , string $subject , string $message [, string $additional_headers [, string $additional_parameters ]] )

0. 我的代码

根据邮件协议,使用基本的php代码进行编写,直接包含这个类即可使用

1. 发送html

  1. 发送html就是将正文的message以文本格式发送

    2. 发送附件

  2. 附件需要以multipart/form-data的格式进行组装,这儿与web上传form表单的格式一样。例子如下

    --==Mime_Multipart_Boundary_x69c8f8864502559be8e17a0cb379ff0cx
    Content-Transfer-Encoding: base64
    
    54eV5YevQm9keQoKCgo=
    
    --==Mime_Multipart_Boundary_x69c8f8864502559be8e17a0cb379ff0cx
    X-Attachment-Id: 15259
    Content-Transfer-Encoding: base64
    Content-Type: application/octet-stream; name="keything.txt"
    Content-Disposition: attachment; filename="keything.txt"
    
    54eV5Yev
    
    --==Mime_Multipart_Boundary_x69c8f8864502559be8e17a0cb379ff0cx--
    
  3. 我们所发出的数据就如上面文本所示,因此要将其组装
  1. class KTMail

    <?php
    class KTMail
    {
        const UTF8_PREFIX  = '=?UTF-8?B?';
        const UTF8_POSTFIX = '?=';
        const RN = "\r\n";
        /**
         * @param from: who send this mail
         * @param to: who receive this mail
         * @param subject: the title of mail
         * @param body: the content of mail
         * @param attachment_fname: the file name of attachement
         * @param attachment_fdata: the file content of attachment
         * @return true if send succeed, FALSE otherwise
         * TODO if attachment_fname is chinese, it will be messay code.
         */
        public static function sendWithAttach($from, $to, $subject, $body, $attachment_fname, $attachment_fdata)
        {
            // a random string 
            $semi_rand = md5(time()); 
            $mime_boundary = '==Mime_Multipart_Boundary_x' . $semi_rand . 'x';
            $part_boundary = '==Part_Multipart_Boundary_x' . $semi_rand . 'x';
    
            // header 
            $headers = [];
            $headers[] = 'MIME-Version: 1.0';
            $headers[] = 'Content-Type: multipart/mixed; boundary=' . $mime_boundary;
            $headers[] = 'From: ' . $from;
            $headers_raw = implode(self::RN, $headers);
    
            // Message Body
            $msg = [];
            $msg[] = '--' . $mime_boundary;
            $msg[] = 'Content-Transfer-Encoding: base64' . self::RN;
            $msg[] =  chunk_split(base64_encode($body));
    
            // Attachment
            $msg[] = '--' . $mime_boundary;
            $msg[] = 'X-Attachment-Id: ' . rand(1000, 99999);
            $msg[] = 'Content-Transfer-Encoding: base64';
            $msg[] = 'Content-Type: application/octet-stream;' . ' name="' . $attachment_fname . '"';
            $msg[] = 'Content-Disposition: attachment; filename="'. $attachment_fname . '"' . self::RN;
            $msg[] = chunk_split(base64_encode($attachment_fdata));
            $msg[] = '--' . $mime_boundary . '--';
    
            $msg_raw = implode(self::RN, $msg);
            error_log($msg_raw, 3, '/tmp/sendmail.log');
    
            $real_subject = self::UTF8_PREFIX . base64_encode($subject) . self::UTF8_POSTFIX;
    
            return mail($to, $real_subject, $msg_raw, $headers_raw);
        }
        public static function sendWithHtml($from, $to, $subject, $body)
        {
            // header 
            $headers = array();
            $headers[] = 'MIME-Version: 1.0';
            $headers[] = 'Content-type: text/html; charset=utf-8';
            $headers[] = 'From: ' . $from;
            $headers_raw = implode(self::RN, $headers);
    
            // Message Body
            $real_subject = self::UTF8_PREFIX . base64_encode($subject) . self::UTF8_POSTFIX;
            $msg_raw = $body;
    
            return mail($to, $real_subject, $msg_raw, $headers_raw);
        }
    }
    
  2. 测试用例

    <?php
    include ('ktmail.php');
    $from = 'local@a.cn';
    $to = 'your-email';
    $subject = 'keything.net';
    $body = 'keything.net';
    $attachment_fname = 'keything.txt';
    $attachment_fdata = 'keything';
    $attach_res = KTMail::sendWithAttach($from, $to, $subject, $body, $attachment_fname, $attachment_fdata);
    echo 'attach_res = ' . var_export($attach_res, true)."\n";
    
    $html_body = '
        <html>
            <head> keything </head>
            <body> 
                keything body 
                <table border="1">
                    <tr>
                        <td>row 1, cell 1</td>
                        <td>row 1, cell 2</td>
                    </tr>
                    <tr>
                        <td>row 2, cell 1</td>
                        <td>row 2, cell 2</td>
                    </tr>
                </table>
            </body>
            </head>
        </html>
        ';
    $html_res = KTMail::sendWithHtml($from, $to, $subject, $html_body);
    echo 'html_res = ' . var_export($html_res, true)."\n";
    

nginx模块执行顺序

整篇文章参考

  1. http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html
  2. http://www.nginxguts.com/2011/01/phases/

一、概述

1. 简要介绍

在每个阶段都可以注册多个自定义的handlers。但是下面的几个阶段是不能够注册自定义的handler的:

1. find-config阶段
2. post-access阶段
3. post-rewrite阶段
4. try-files阶段

每个阶段都有一个与之相关的handler的列表。一旦把handler注册到对应阶段,那么handler就会返回下面的取值:

NGX_OK:请求已经成功处理,请求将会传到下一个阶段。

NGX_DECLINED:请求需要被转发到本阶段的下一个handler

NGX_AGAIN,NGX_DONE:请求已经被正确处理,同时请求被挂起,直到某个事件(子请求结束、socket可写或超时等)到来,handler才会再次被调用

2. 如何注册自定义的handler

为了在某个阶段注册自定义的handler,需要找到ngx_http_core_module,并添加(ngx_array_push)到指定的phrase向量中。handlers以相反的顺序被调用。因此在配置中最后注册的handler会首先被调用。

从上面可以看出,请求处理的顺序和配置文件中的配置指令的先后顺序无关,无论配置文件中指令的顺序如何,各个阶段的处理函数都会按照预先的顺序执行。因此一个处理函数需要知道自己什么时候会被调用,何时需要返回NGX_DECLINED,而且保证减少性能损耗。

3. 各个phase对于返回值的处理:

  1. Access阶段

    NGX_OK:允许访问请求URI指定的资源
       NGX_HTTP_FORBIDDEN,NGX_HTTP_UNAUTHORIZED:不允许访问请求URI指定的资源
    
  2. Content阶段

     NGX_AGAIN或NGX_DONE时,content handler不会再被调用。取而代之的handler改变请求的读或写的handler
    如果content handler返回NGX_DECLINED, nginx将会将请求转发到content phrase handlers。
    
  3. 关于content phase handler与content handler的区别

a. content phase handler是混乱的:每个到达content阶段的请求都可以调用content phase handler. 对于配置content handler的location,这些请求只能调用一次content handler。

b. 在一个location中可以调用多个content phase handler。但是在一个location中只能有一个content handler。

这儿借用agentzh 文章Nginx配置指令的执行顺序(五)中例子。

location /test {
    echo_before_body "before...";
    proxy_pass http://127.0.0.1:8080/foo;
    echo_after_body "after...";
}

location /foo {
    echo "contents to be proxied";
}

测试结果表明这一次我们成功了:

$ curl 'http://localhost:8080/test'
before...
contents to be proxied
after...

因此我们知道echo_before_body 和echo_after_body指令都属于content phase handler。也就是Agentzh文中的输出过滤器。

二、详细介绍

Nginx 处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log.

1. post-read

  1. 支持模块注册处理程序
  2. 发生阶段:nginx读取并解析完请求头之后立即开始执行
  3. 示例模块:标准模块 ngx_realip,目的:改写请求的来源地址。
  4. 举例

    server {
        listen 8080;
    
        set_real_ip_from 127.0.0.1; # 规定来源地址的改写只对那些来自127.0.0.1请求生效
        real_ip_header   X-My-IP;
    
        location /test {
            set $addr $remote_addr;
            echo "from: $addr";
        }
    }
    

    结果

    $curl -H 'X-My-IP: 1.2.3.4' your.domain/test
    from: 1.2.3.4
    

2. server-rewrite

  1. 支持模块注册处理程序

3. find-config

  1. 不支持模块注册处理程序
  2. 目的:由nginx核心完成当前请求与location配置块之间的配对工作。在此阶段之前,请求没有与任何location配置块相关联。

4. rewrite

  1. 支持模块注册处理程序
  2. 目的:对当前请求进行各种修改,比如URI,URL,或者创建并初始化一系列后续处理阶段所需要的变量

5. post-rewrite

location级别重写的后一阶段,用来检查上阶段是否有uri重写,并根据结果跳转到合适的阶段;

  1. 不支持模块注册处理程序
  2. 目的:nginx核心完成rewrite阶段所要求的“内部跳转”操作。“内部跳转”本质上其实是把当前请求的处理阶段倒退到find-config阶段,以便重新进行请求URI与location配置块的配对。
  3. 注意:有趣的地方是,倒退回find-config阶段的动作并不是发生在rewrite阶段,而是发生在后面的POST-REWRITE阶段。rewrite指令只是简单地指示Nginx有必要在POST-REWRITE阶段发起“内部跳转”。这个设计的目的是:为了在最初匹配的location块中支持多次反复地改写URI。

    location /foo {
    rewrite ^ /bar;
    rewrite ^ /baz;
    
    echo foo;
    }
    
    location /bar {
        echo bar;
    }
    
    location /baz {
        echo baz;
    }
    

    请求

     $ curl localhost:8080/foo
    baz
    

    日志

    $ grep 'using config' logs/error.log
       [debug] 89449#0: *1 using configuration "/foo"
       [debug] 89449#0: *1 using configuration "/baz"
    
  4. server配置块中的rewrite指令

    server配置块中的rewrite指令对请求URI进行改写,则不会涉及”内部跳转“,因为此时URI改写发生在server-rewrite阶段,早于location配对的find-config阶段。

6. pre-access

  1. 标准模块ngx_limit_reqngx_limit_zone 就运行在此阶段,前者控制请求的访问频度,后者限制访问的并发度。
  2. 【建议】 尽量在server配置块中配置ngx_realip这样的模块

7. acess

  1. 标准模块 ngx_access
  2. 第三方模块 ngx_auth_request, access_by_lua

8. post-access阶段

  1. 不支持模块注册处理程序
  2. 目的:主要配合acess阶段实现标准ngx_http_core模块提供的配置指令satisfy的功能。在access阶段注册多个处理指令,satisfy指令可以用于控制它们彼此之间的协作方式。有两种协作方式,一种是all(与关系),一种是any(或关系)。
  3. 举例:

    location /test {
    satisfy all;
    
    deny all;
    access_by_lua 'ngx.exit(ngx.OK)';
    
    echo something important;
    }
    

    请求

    satisfy all; $curl your.domain/test ----403 forbidden
    satisfy any; $curl your.domain/test ---- something important
    

9. try-files

  1. 不支持模块注册处理程序
  2. 目的:专门实现标准配置指令try_files功能
  3. try_files 指令接受两个以上任意数量的参数,每个参数都指定了一个 URI. 这里假设配置了 N 个参数,则 Nginx 会在 try-files 阶段,依次把前 N-1 个参数映射为文件系统上的对象(文件或者目录),然后检查这些对象是否存在。一旦 Nginx 发现某个文件系统对象存在,就会在 try-files 阶段把当前请求的 URI 改写为该对象所对应的参数 URI(但不会包含末尾的斜杠字符,也不会发生 “内部跳转”)。如果前 N-1 个参数所对应的文件系统对象都不存在,try-files 阶段就会立即发起“内部跳转”到最后一个参数(即第 N 个参数)所指定的 URI.
  4. try_files 指令本质上只是有条件地改写当前请求的 URI,而这里说的“条件”其实就是文件系统上的对象是否存在。当“条件”都不满足时,它就会无条件地发起一个指定的“内部跳转”。当然,除了无条件地发起“内部跳转”之外, try_files 指令还支持直接返回指定状态码的 HTTP 错误页,例如try_files /foo /bar =404;
  5. 特别注意:对于try_files指令,通过URI末尾的斜杠字符来区分”目录“和”文件“的。

     root /var/www/;
    
    location /test {
        try_files /foo /bar/ /baz;
        echo "uri: $uri";
    }
    
    location /foo {
        echo $uri;
        echo "foo";
    }
    
    location /bar/ {
           echo $uri;
         echo bar;
    }
    
    location /baz {
        echo $uri;
        echo baz;
    }
    

case1 :根目录下没有foo文件,bar目录,输出/baz, baz

$ grep trying logs/error.log
[debug] 3869#0: *1 trying to use file: "/foo" "/var/www/foo"
[debug] 3869#0: *1 trying to use dir: "/bar" "/var/www/bar"
[debug] 3869#0: *1 trying to use file: "/baz" "/var/www/baz"

这里最后一条“调试信息”容易产生误解,会让人误以为 Nginx 也把最后一个参数 /baz 给映射成了文件系统对象进行检查,事实并非如此。当 try_files 指令处理到它的最后一个参数时,总是直接执行“内部跳转”,而不论其对应的文件系统对象是否存在。

case2 :根目录下有foo文件,输出 uri: /foo

10. content

  1. 目的:内容生成阶段。肩负着生成“内容”, 并输出HTTP响应的使命。echo, proxy_pass都属于该阶段。

  2. 对于content阶段,有两种情况:

    a. 没有使用任何content阶段的指令。那么就会执行静态资源服务模块:ngx_index, ngx_autoindex, ngx_static模块

    b. 如果使用了content阶段的指令,如echo,proxy_pass。

    每个location只能有一个内容处理程序content handler.有多个时content阶段指令时,只有一个能够被注册并执行。
    

    比如指令中有 echo ‘hello’;proxy_pass so.com;到底哪个被执行是不一定的。因为在同一个阶段phase中,各个handler的执行顺序是不一定的。

11. log

nginx-rewrite详解

一. 概述

ngx_http_rewrite_module模块通过正则表达式、return重定向和条件选择配置(conditionally select configurations)改变请求的URI。
ngx_http_rewrite_module模块中的指令以下面的顺序执行:

1. 在server{}中的指令先被执行,并且是根据配置中先后顺序依次执行
2. 重复的:
    1. 根据请求URI匹配一个location
    2. 在匹配的location中,该模块的指令按先后顺序依次执行
    3. 如果一个请求URI被重定向,这个循环被重复,但最多不超过10次。

【解释】在ngx_http_rewrite_module初始化的时候(ngx_http_rewrite_init)的时候,在SERVER_REWRITEREWRITE两个阶段注册了相同的处理函数ngx_http_rewrite_handler.

二. 指令

1. break

Syntax:break;
Default:—
Context:server, location, if

停止处理ngx_http_rewrite_module模块的指令。
如果在location里面使用,那么这个请求接下来phase的处理也都是在这个location里面。
看下面的例子

location /proxy3 {
    rewrite (.*) /third;
    set $a 35;
    break;
    rewrite (.*) /second;
    set $a 76;
    echo $a;
}
location /second {
    echo 'second';
    echo $a;
}
location /third {
    echo 'third';
    echo $a;
}

curl http://your.domain/proxy3

case result reason
有break 输出35
没有break 输出second 76 因为4条指令顺序执行,rewrite (.*) /second是最后一条rewrite指令,生效。set $a 76生效。因此在POST-REWRITE阶段转到location /second

2. if

Syntax: if (condition) {...}
Default: ---
Context: server, location

指定的condition参数被判断。如果为true,在if里面的模块指令被执行,请求进入到if指令里面的配置。在if指令里面的配置继承于上一层的配置。
condition可以是下面几种情况:

1. 一个变量名; 如果变量是空字符串或0,则false
2. 使用= !=运算符比较
3. 使用~(区分大小写) ~*(不区分大小写) 正则表达式包含capture,可以使用$1...$9来使用捕获的值。!~ !~*也可以使用.如果正则表达式包含} ;的话,整个正则表达式要放在单引号或双引号里面。
4. 检查文件是否存在 -f !-f
5. 检查目录是否存在 -d !-d
6. 检查文件,目录,软链是否存在 -e !-e
7. 检查是否可执行文件 -x !-x

举例:

if ($http_user_agent ~ MSIE) {
       rewrite ^(.*)$ /msie/$1 break;
}

if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
    set $id $1;
}

if ($request_method = POST) {
    return 405;
}

if ($slow) {
    limit_rate 10k;
}

if ($invalid_referer) {
    return 403;
}

3. return

Syntax: return code [text];
        return code URL;
        return URL;

停止处理,并将code返回给客户端。非标准code444 关闭连接并且不发送响应头。
从版本0.8.42开始,可以指定一个URL(对于301,302,303和307)或者响应体(其他code)。一个响应体和重定向的URL可以包含变量。
另外,URL可以指定为单个参数,这样就是302临时跳转。这样一个参数需要以http://, https://, $scheme开头。

4. rewrite

Syntax: rewrite regex replacement [flag];
Default: ---
Context: server, location, if

如果指定的正则表达式匹配了一个请求的URI,URI被指定的replacement改变。

配置文件中多个rewrite指令时,按照出现顺序依次执行,只执行最后一个。可以使用[flags]终止指令的处理。

如果replacement以http:// https:// 开头,那么处理将会停止,并重定向返回给客户端。

可选参数flag,可以是下面几个中的某一个:

last:
    停止处理ngx_http_rewrite_module模块的指令集,然后使用改变后的URI去查找新的location。
    【注释】即遇到last以后,执行阶段从rewrite阶段回到了find-config阶段。
break:
    停止处理ngx_http_rewrite_module模块的指令集。
    【注释】遇到break以后,执行阶段从rewrite阶段转到下一个阶段。如果是server-rewrite则转到find-config, 如果是rewrite则转到post-rewrite。
redirect:
    返回一个302临时跳转。当replacement不是以http://, https://开头的时候使用
permanent:
    返回一个301永久跳转。

last其实就相当于一个新的url,对nginx进行了一次请求,需要走一遍大多数的处理过程,最重要的是会做一次find config,提供了一个可以转到其他location的配置中处理的机会,

而break则是在一个请求处理过程中将原来的url(包括uri和args)改写之后,在继续进行后面的处理,这个重写之后的请求始终都是在同一个location中处理。

5. rewrite_log

Syntax: rewrite_log on | off;
Default: rewrite_log off;
Context: http,server,location,if

开启或关闭ngx_http_rewrite_module模块指令处理结果到error_log文件,日志级别notice.

三. 例子

  1. echo $a被if继承。因此输出45

    location /proxy3 {
         echo $a;
         set $a 35;
         if ($a = 35) {
             set $a 45;
         }
    }
    
  2. echo $a被if继承,但if指令里面有echo指令,根据配置的基本规则,if里面的echo指令有效。因此输出43 【todo】配置的基本原则是毛线???

    location /proxy3 {
         echo $a;
         set $a 35;
         set $b 43;
         if ($a = 35) {
             set $a 45;
             echo $b;
         }
    }
    
  3. 由于if处于rewrite阶段,执行if以后依然处于rewrite阶段,因此if{}后面的模块指令依然执行。
    因此输出67;

    location /proxy3 {
         echo $a;
         set $a 35;
         if ($a = 35) {
             set $a 45;
         }
         set $a 67;
    }
    
  4. 演示rewrite

    location /proxy3 {
        #rewrite (.*) http://your.domain/third;
        rewrite (.*) /third;
        set $a 67;
        echo $a;
    }
    set $a 99;
    location /third {
        echo $a;
    }
    

    如果rewrite后面是/third 则输出67
    如果是http:// 则输出99。
    解释:
    set $a 99 发生在server-rewrite阶段
    之后在find-config阶段,找到location /proxy3
    在location /proxy3里面 如果是rewrite http://...那么直接跳转。类似 rewrite (.) /third redirect;
    如果是rewrite (.
    ) /third呢 则还会继续执行接下来的rewrite阶段的指令,set $a 67将会被执行。然后在post-rewrite阶段调转到location /third

  5. rewrite中带有flag

    location /proxy3 {
          set $a 35;
          if ($a = 35) {
              set $a 73;
              rewrite (.*) /main break;
          }
          rewrite (.*) /second;
          set $a 76;
          echo 'proxy3';
          echo $uri;
          echo $a;
      }
      location /main {
          echo 'main';
          echo $a;
      }
      location /second {
          echo 'second';
          echo $a;
      }
    

    如果是rewrite (.*) main break 。 则输出proxy3 /main 73

    如果是rewrite (.*) main; break;,则输出proxy3 /main 73
    原因是而break则是在一个请求处理过程中将原来的url(包括uri和args)改写之后,在继续进行后面的处理,这个重写之后的请求始终都是在同一个location中处理;

    如果是rewrite (.*) main last,则输出main 35

    如果是rewrite (.*) main 不加break,last, 则rewrite (.*) /second; set $a 76执行 输出 second 76

  1. 多个rewrite只有最后一个起作用

    location /proxy3 {
        rewrite (.*) /third;
        set $a 35;
        if ($a = 35) {
            rewrite (.*) /main;
        }
        rewrite (.*) /second; 
        set $a 76;
        echo $a;
    }
    

四. 几点事实:

  1. rewrite 指令被称为 action directives. 下一层级的location不会从上一级中继承。
  2. server 作用域中的 rewrite 模块指令也不会向下传递到 location 作 用域,但是这些指令会在 SERVER_REWRITE 阶段 (先于 REWRITE 阶段) 被执行.
  3. rewrite 模块提供的指令的执行顺序和其在配置文件中的定义顺序一致
  4. if 指令在nginx内部创建了一个无名location,if条件为真时,nginx使用这个无名location作用域的配置处理当前请求。

  5. rewrite模块
    rewrite 模块是一个 phase handler, 其初始化函数 ngx_http_rewrite_init 在 SERVER REWRITE 和 REWRITE 阶段 注册了相同的处理函数ngx_http_rewrite_handler。其中,SERVER REWRITE 阶段 的处理函数用于执行 server 作用域中的 rewrite 模块指令,而 REWRITE 阶 段的处理函数用于执行 location 和 if 作用域的 rewrite 模块指令。

  6. POST REWRITE 是 Nginx 内部定义的阶段,通过检查请求 uri 是否被 rewrite 模块修改 (r->uri_changed),判断是否需要使用修改后的 uri 重新开始 FIND CONFIG 以重新匹配合适的 location。比如在 location 中有配置如 rewrite … last; 且 rewrite 成功和请求 uri 匹配成功时。

  1. 指令执行顺序

nginx大部分模块提供的配置指令的生效顺序和其在配置文件中的顺序并没有什么关系。但有些是有关系的,比如

1. http core模块中使用正则表达式的location。跟顺序有关
2. rewrite模块中提供的if,break,return,rewrite指令

五. 参考文章

http://ialloc.org/posts/2015/07/28/ngx-notes-http-evil-if-1/
http://nginx.org/en/docs/http/ngx_http_rewrite_module.html
https://blog.martinfjordvald.com/2012/08/understanding-the-nginx-configuration-inheritance-model/
http://ialloc.org/posts/2013/07/18/ngx-notes-conf-parsing/
http://blog.csdn.net/brainkick/article/details/7475770

nginx-location匹配

这篇文字是对官方文档中location[1]:http://nginx.org/en/docs/http/ngx_http_core_module.html#location 的解读。

一. 语法

Syntax:

location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }

Default:

—

Context:

server, location

二. 解释

根据请求URI进行配置。

与归一化的URI进行匹配。 归一化包括:1)将已经编码为%XX格式的URI解码为普通格式; 2)解析references到相对路径. 和..; 3)将多个连接的斜杠/ 变为单个斜杠/。

  1. 一个location可以是前缀字符串,也可以是正则表达式。
  2. 正则表达式由 ~*(大小写不敏感) 或 ~(大小写敏感)指定。
  3. 为了找到与给定请求相匹配的location,nginx首先检查前缀字符串(前缀location)定义的location。在这些前缀字符串中,选择最长匹配的前缀并将其记住。
  4. 之后检查`正则表达式,检查正则表达式是按照其在配置文件中的顺序检查,当正则表达式第一次匹配后,就停止正则表达式的匹配,对应的正则的location的配置就会被使用。
  5. 如果没有与正则表达式相匹配的location,那么最长前缀location就被使用。
  6. 如果最长匹配前缀location有^~的修饰符,那么就不会检查正则表达式。
  7. 同时,可以使用=修饰符,该修饰符定义了URI和location的准确匹配。如果一个准确的match被找到,那么搜索停止。

location检查的策略:

1. 首先检查指定了前缀的location块 普通的location块。
2. 其次检查带正则的location块 顺序匹配,一旦匹配到第一个就停止后面的匹配。
对于匹配正则location并不是所有情况都进行,有两种情况是例外:
1)普通location前面指定了^~ 告诉nginx只要普通location一旦匹配上,则不需要继续正则匹配。匹配上了普通location就不进行正则匹配。
2)普通location恰好严格匹配上,不是最大前缀匹配,则不再继续匹配正则。

   简要概述:
    正则 location 匹配让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果
如下图所示:

location_match

举例说明:

location = / {
    [ configuration A ]
}

location / {
    [ configuration B ]
}

location /documents/ {
    [ configuration C ]
}

location ^~ /images/ {
    [ configuration D ]
}

location ~* \.(gif|jpg|jpeg)$ {
    [ configuration E ]
}

请求/ 匹配配置A,/index.html匹配配置B,/documents匹配配置C,/images/1.gif匹配配置D,/documents/1.jpg匹配配置E

注意:@前缀定义了一个命名location。这样一个location不是用于正则请求处理,而是用于请求的重定向。他们不能嵌套在location中,也不能包含嵌套的location。

参考文章

http://nginx.org/en/docs/http/ngx_http_core_module.html#location

nginx配置-最小配置

前述

很多时候都会提到最小模块。对于Nginx的配置,当然也要有最小配置呢。当然下面的配置也不是最小配置,因为里面有很多配置都是有默认值的。大家可以查看一下nginx.org上的文档说明。

主配置文件nginx.conf详细说明

在nginx.conf中包括了事件模块events{}, http模块http{}。并没有看到预想的虚拟主机server{}。因为我们通过include host/*.conf将虚拟主机的配置放在了vhost文件夹中。类似C语言中include。

##nginx.conf
error_log  /usr/local/var/log/nginx/error.log  info;
pid        /usr/local/var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    include vhost/*.conf;
}

普通HTTP虚拟主机配置

vhost/basic.conf
basic.conf配置中必要的配置是listen,root,location /, location ~ .php$ {}.
其中location ~.php${}的作用是将以php结尾的文件转向FastCGI服务器。

# vhost/basic.conf
server {
    listen       80 default;
    server_name  localhost;
    access_log  /usr/local/var/log/nginx/access.log;

    charset utf8;

    root   /Users/xx/Documents/Study/nginx/learn_conf;
    location / {
        index  index.php index.html index.htm;
    }

    error_page 404 404.php;
    fastcgi_intercept_errors on;
    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    location ~ \.php$ {
        fastcgi_pass   localhost:9000;
        fastcgi_index  index.php;
        include        fastcgi.conf;
    }
}

加密HTTPS虚拟主机配置

加密HTTPS与普通HTTP虚拟主机的区别在于:
1)端口号后加 ssl
2)加入ssl_certificate ssl_certificate_key两个指令

其他方面二者相同:比如端口号改变,server_name更改

server {
    listen 443 ssl;
    server_name learn.ssl1;

    ssl_certificate /Users/Documents/GitLibrary/nginx/nginx_conf/vhost/33iq.crt;
    ssl_certificate_key /Users/Documents/GitLibrary/nginx/nginx_conf/vhost/33iq_nopass.key;

    root /Users/Documents/Study/nginx/learn_ssl2;
    location / {
        index index.php index.html index.htm;
    }

    error_page 404 404.php;
    fastcgi_intercept_errors on;
    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    location ~ \.php$ {
        fastcgi_pass   localhost:9000;
        fastcgi_index  index.php;
        include        fastcgi.conf;
    }
}

Unix/Linux文件系统--目录,inode,硬链接

1. 简介:文件和Inodes

在Unix/Linux中,文件是没有结构的一个字节序列。文件中控制数据的程序(比如mysql)会添加任何所需要的结构。linux自己并不需要知道数据库文件的内部结构,对于linux而言只需要返回字节(bytes)

1.1 甚至硬件设备都有文件名

Unix/Linux尽可能的将每个设备都当做一系列字节。因此包括网卡、硬盘驱动、键盘、打印机、文件在内,一切都被当做文件。

你的电脑内存是 /dev/mem
你的第一块硬盘是 /dev/sda
你的终端是 /dev/tty1
...

举例

$ ls -li /dev/mem /dev/sda /dev/tty1
5792 crw-r----- 1 root kmem 1, 1 Oct 13 02:30 /dev/mem
 888 brw-rw---- 1 root disk 8, 0 Oct 13 02:30 /dev/sda
5808 crw-rw---- 1 root tty  4, 1 Oct 13 02:31 /dev/tty1

在Linux中大多数的输入输出设备和目录都被看做文件。如果你有足够的权限,你可以使用它们的文件系统的名字来读取这些设备。

1.2 Index Nodes = inodes

文件系统的内容都不是以名字的形式存储,而是以数字的形式存储。Linux是以序号化结构来存储每个硬盘对象(disk object, 如文件或目录)的数据和信息。这个序号化的结构称之为index node或inode。

每个inode都是由唯一的inode number来标识,可以使用ls -i选项查看。

$ ls -l -i /usr/bin/perl*
266327 -rwxr-xr-x 2 root root 10376 Mar 18  2013 /usr/bin/perl
266327 -rwxr-xr-x 2 root root 10376 Mar 18  2013 /usr/bin/perl5.14.2
266331 -rwxr-xr-x 2 root root 45183 Mar 18  2013 /usr/bin/perlbug
266328 -rwxr-xr-x 1 root root   224 Mar 18  2013 /usr/bin/perldoc

Unix的目录将文件系统的名字与inode number进行关联。正如上面例子,/usr/bin/perl与inode number 266327关联。当你到达perl程序的时候,系统将会在目录中找到perl这个名字,与perl名字相关联的inode number是266327,这个inode存有真正的数据,系统通过inode找到真正的数据。文件数据与inode number关联,而不是与名字关联。

在目录中,每个文件有自己名字和与之关联的inode number。每个文件名字可以映射到一个inode number,但是一个inode number可以有多个名字。

Inode numbers are specific to a file system inside a disk partition. Every file on a file system (in that partition) has a unique inode number. Numbering is done separately for each file system, so different disk partitions may have file system objects with the same inode numbers.

每个文件系统初始化的时候会有大量可用的inodes,可以使用df -i列出可用的inodes。

2. 文件系统示意图

大多数关于文件系统的示意图都是错误的,下面是真相。
inodes的名字(如文件、目录、设备等的名字)是在目录中存储的。目录中只存储了名字和inodenumber的关联关系。文件中数据的真正存储的硬盘空间是在inode中,而不是在目录中。目录中保存的是名字和inode number,名字不会与数据有关联。

目录中,每个名字有一个inode number,inode number指出了存储数据的硬盘空间的位置。通过ls -i看到名字和inode对应关系。

$ ls -i /usr/bin/perl*
266327 /usr/bin/perl        266329 /usr/bin/perldoc.stub
266327 /usr/bin/perl5.14.2  266330 /usr/bin/perlivp
266331 /usr/bin/perlbug     266331 /usr/bin/perlthanks
266328 /usr/bin/perldoc

最重要的事情是知道名字和数据真正存储的位置是分开的。大多数示意图都会把ROOT的名字搞成命名的。这会造成对unix/linux文件和目录的误解。

WRONG - names on things      RIGHT - names above things
=======================      ==========================

    R O O T            --->         [etc,bin,home]   <-- ROOT directory
   /   |   \                         /    |      \
etc   bin   home       --->  [passwd]  [ls,rm]  [abcd0001]
 |   /   \    \                 |      /    \       |
 |  ls   rm  abcd0001  --->     |  <data>  <data>  [.bashrc]
 |               |              |                   |
passwd       .bashrc   --->  <data>                <data>

目录是名字和数字的列表,右侧示意图以中括号的方式列出,在示意图中忽略了innode number。目录中每一个对象(文件、目录、特殊文件等)的名字与真正的存储空间是分开的。这就允许一个inode可以有多个名字和多个目录。使用相同的inode number,所有的名字指向相同的存储空间。

右侧示意图中,树中目录有自己的名字。右侧最顶层目录是ROOT目录的inode,包含了etc,bin,home..的名字列表。因为在ROOT目录上面没有目录,因此ROOT目录是没有名字的。

ROOT目录下的名字bin与之关联的inodenumber是一个目录的inodenumber,包含了在bin目录下的names的列表,names列表包含名字ls和rm。从bin目录下的ls往下走就是文件/bin/ls的数据节点了。数据节点中不会包含文件的名字,名字在文件的目录中保存。

ROOT节点没有名字,因为ROOT节点上面没有目录了。其他目录有名字是因为在这些目录上面还有目录,上层目录包含了其名字。

3 Inodes Manage disk blocks

每个Uninx文件或目录储存在硬盘的真正数据是由序号化的硬盘数据结构进行管理的,序号化的硬盘数据结构称为Inodes。每个文件和每个目录都分配一个inode。

Unix节点管理每个文件或目录的硬盘存储空间。inode中包含了一系列指针,指向属于那个文件或目录的硬盘块(disk blocks)。文件或目录越大,inode中需要的硬盘块指针就越多。inode还存储文件或目录的属性(权限、所有者、group、size,access/modify times, etc.) ,唯独没有文件或目录的名字。Inodes只有数字、属性和硬盘块–没有名字。名字分开存储,在目录中进行保存。

4. 目录节点保存所有的名字

文件有硬盘块用于包含文件数据,目录同样有硬盘块,保存的是名字和inode number的对应关系。

像大多数其他的节点,目录节点也包含了节点的属性信息(权限、所有者等等)和一个或多个硬盘块指针用来存储数据。但是,目录的硬盘块存储的不是文件数据而是目录的数据:名字和inode numbers。

5. 多个名字–硬连接

因为

1. 一个唯一数字的inode管理一个文件
2. inode中不会保存文件的名字
3. 目录中保存文件的名字和inodenumber对应关系

因此

一个unix文件可以在一个或多个目录中有多个name-and-inode对,从而有多个名字。

硬链接hardlinks:相同inode有多个名字。
ln命令为目录中已经存在的节点创建一个新的名字。系统在每个inode中维持一个link count的字段,用于对inode指向的names进行计数。rm命令从目录中删除一个名字(hark link),并减少link count。当一个inode的link count减为0以后,inode就没有名字了,inode就会被回收,所有与inode关联的存储和数据都被释放了。

rm命令不是删除文件,而是删除文件的名字。当所有的名字都没有了,系统会删除文件并且释放空间

6. Tracing Inodes in Pathnames

参考文章

http://teaching.idallen.com/cst8207/13w/notes/450_file_system.html

Lvs学习笔记

概述

在使用lvs的过程中,我一直担心一个问题:lvs能够抗住最大的并发是多少?当并发非常大的时候,lvs这一台机器的性能会不会变得很差?

我去查看了章文嵩的文章http://www.linuxvirtualserver.org/zh/lvs1.html
发现以下几个点:

1. lvs是基于ip层和基于内容请求分发的负载平衡调度。基于ip层和基于内容请求分发两种方式

2. IP负载均衡技术有三种:VS/NAT, VS/TUN,VS/DR。

基于IP负载调度技术中,当一个TCP连接的初始SYN报文到达时,调度器就选择一台服务器,将报文转发给它。此后通过查发报文的IP和TCP报文头地址,保证此连接的后继报文被转发到该服务器。这样,IPVS无法检查到请求的内容再选择服务器,这就要求后端服务器组提供相同的服务,不管请求被发送到哪 一台服务器,返回结果都是一样的

3. VS/NT virtual server via Network Address Translation

通过网络地址转换,调度器重写请求报文的目标地址,根据预设的调度算法,将请求分派给后端的真实服务器;真实服务器的响应报文通过调度器时,报文的源地址被重写,再返回给客户,完成整个负载调度过程。

假如结构如下图所示,client要与服务器端建立连接,在lvs层ip包的目的地址被改为node1,那么该TCP连接的初始SYN报文送到node1,node1返回ACK报文给lvs层,lvs将ACK报文的源地址修改为lvs。以后通过查找发报文的IP和TCP报文头地址,保证此连接的后续报文被转发到node1。 如果我们在client通过tcpdump抓包的话,看到的都是client和lvs之间的交互。

                 _ _ _ node1
                /
client---> lvs ------  node2
                \_ _ _ _ node3

4. LVS集群的性能

LVS服务器集群系统具有良好的伸缩性,可支持几百万个并发连接。配置100M网卡,采用VS/TUN或VS/DR调度技术,集群系统的吞吐量可高达1Gbits/s;如配置千兆网卡,则系统的最大吞吐量可接近10Gbits/s。

因为工作在IP层,只用作分发,因此没有流量产生,内存和CPU资源消耗很低,IO性能不会受到大流量的影响

6. memcache获取所有的key及value

0. 特别注意:

通过cachedump命令只能获取1M的数据,无法获取更多的数据。

1. 命令行来查看memcache的key及取值

  1. stats slabs 查看有哪些slab

    STAT 18:chunk_size 4544
    STAT 18:chunks_per_page 230
    STAT 18:total_pages 1
    STAT 18:total_chunks 230
    STAT 18:used_chunks 0
    STAT 18:free_chunks 7
    STAT 18:free_chunks_end 223
    STAT 18:mem_requested 18446744073709551411
    STAT 18:get_hits 777
    STAT 18:cmd_set 103
    STAT 18:delete_hits 0
    STAT 18:incr_hits 0
    STAT 18:decr_hits 0
    STAT 18:cas_hits 0
    STAT 18:cas_badval 0

  2. stats cachedump slab_id limit

    将slab为slab_id的数据展示出来。 limit是展示的个数,如果取值为0, 则不作限制(其实是限制了1m)

http://blog.elijaa.org/2010/12/24/understanding-memcached-stats-cachedump-command/
http://blog.elijaa.org/2010/05/21/memcached-telnet-command-summary/#slabs_stats

2. php代码来获取

<?php
$mcobj = new Memcache();
$mcobj->addServer('xxx',9150);

$to_mcobj = new Memcache();
$to_mcobj->addServer('xxx', 21212);

$allSlabs = $mcobj->getExtendedStats('slabs');
$items    = $mcobj->getExtendedStats('items');
foreach ($allSlabs as $server => $slabs) {
    foreach ($slabs as $slabId => $slabMeta) {
        $cdump = $mcobj->getExtendedStats('cachedump', (int)$slabId, 0);
        foreach ($cdump as $keys => $vals) {
            foreach ((array)$vals as $k => $v) {
                $real_val = $mcobj->get($k);
                $set_res = $to_mcobj->set($k, $real_val);
                error_log('|slabId='.$slabId.'set--'.'|key='.$k.'|val='.json_encode($real_val).'|res='.$set_res."\n",3,'/tmp/yk.log');
            }
        }
   }
                                                                                            }

4. Memcached常见问题

关于Mc的零散的问题

  1. 如何实现cas的
    很简单,全局维持一个64位的静态变量

    uint64_t get_cas_id(void) {
       static uint64_t cas_id = 0;
       return ++cas_id;
    }
    
  2. Mc启动时-m指定的最大内存maxMem

    1. 并不是一启动,就开始分配内存,而是在需要的时候才进行内存分配
    2. 所指定的最大内存并不是其最大限制,也就是说mc所分配的内存大于maxMem
  3. 一开始大量使用某一大小A的chunk,占据大量的内存,那么当大量使用某一个大小B的chunk时,就会导致B chunk所使用的内存很小,极有可能只有一个slab(默认1m)的内存。那么数据就会不断被覆盖,从而导致mc存储的内容频繁失效。

  4. 分配给大小A的内存,不可以给其他大小的chunk使用。这也导致了上面3的问题
  5. memcached对item的过期时间有什么限制?
    item对象的过期时间最长可以达到30天。memcached把传入的过期时间(时间段)解释成时间点后,一旦到了这个时间点,memcached就把item置为失效状态,这是一个简单但obscure的机制
  6. memcached最大能存储多大的单个item?

    memcached能存储最大1MB的单个item。如果需要被缓存的数据大于1MB,可以考虑在客户端压缩或拆分到多个key中。

  7. 使用不同的客户端库,可以访问到memcached中相同的数据吗?

    从技术上说,是可以的。但是可能会遇到下面三个问题:

    1)不同的库采用不同的方式序列化数据

    举个例子,perl的Cache::Memcached使用Storable来序列化结构复杂的数据(比如hash references, objects等)。其他语言的客户端库很可能不能读取这种格式的数据。如果您要存储复杂的数据并且想被多种客户端库读取,那么您应该以简单的string格式来存储,并且这种格式可以被JSON、XML等外部库解析。

    2)从某个客户端来的数据被压缩了,从另一个客户端来的却没被压缩。

    3)各个客户端库可能使用不同的哈希算法(阶段一哈希)。在连接到多个memcached服务器端的情况下,客户端库根据自身实现的哈希算法把key映射到某台memcached上。正是因为不同的客户端库使用不同的哈希算法,所以被Perl客户端库映射到memcached A的key,可能又会被Python客户端库映射到memcached B,等等。Perl客户端库还允许为每台memcached指定不同的权重(weight),这也是导致这个问题的一个因素

博客中常见问题

http://huoding.com/2012/12/30/205

  1. Cache失效后的拥堵问题
    1. 首先,我们可以主动更新Cache。前端程序里不涉及重建Cache的职责,所有相关逻辑都由后端独立的程序(比如CRON脚本)来完成,但此方法并不适应所有的需求。
    2. 好在我们还有Gearman这根救命稻草。当需要更新Cache的时候,我们不再直接查询数据库,而是把任务抛给Gearman来处理,当并发量比较大的时候,Gearman内部的优化可以保证相同的请求只查询一次后端数据库,以PHP为例,伪代码大致如下:
    <?php

    function query()
    {
        $data = $cache->get($key);

        if ($cache->getResultCode() == Memcached::RES_NOTFOUND) {
            $data = $gearman->do($function, $workload, $unique);
            $cache->set($key, $data, $expiration);
        }

        return $data;
    }

    ?>

说明:如果多个并发请求的$unique参数一样,那么实际上Gearman只会请求一次。
  1. Multiget的无底洞问题

    Facebook在Memcached的实际应用中,发现了Multiget无底洞问题,具体表现为:出于效率的考虑,很多Memcached应用都已Multiget操作为主,随着访问量的增加,系统负载捉襟见肘,遇到此类问题,直觉通常都是通过增加服务器来提升系统性能,但是在实际操作中却发现问题并不简单,新加的服务器好像被扔到了无底洞里一样毫无效果。

  2. 客户端的Hash算法
    http://www.last.fm/user/RJ/journal/2007/04/10/rz_libketama_-_a_consistent_hashing_algo_for_memcache_clients

##适合Mc的业务场景

1)如果网站包含了访问量很大的动态网页,因而数据库的负载将会很高。由于大部分数据库请求都是读操作,那么memcached可以显著地减小数据库负载。

2)如果数据库服务器的负载比较低但CPU使用率很高,这时可以缓存计算好的结果( computed objects )和渲染后的网页模板(enderred templates)。

3)利用memcached可以缓存session数据、临时数据以减少对他们的数据库写操作。

4)缓存一些很小但是被频繁访问的文件。

5)缓存Web ‘services’(非IBM宣扬的Web Services)或RSS feeds的结果.。

不适合Mc的业务场景

1)缓存对象的大小大于1MB

Memcached本身就不是为了处理庞大的多媒体(large media)和巨大的二进制块(streaming huge blobs)而设计的。

2)key的长度大于250字符

3)虚拟主机不让运行memcached服务

如果应用本身托管在低端的虚拟私有服务器上,像vmware, xen这类虚拟化技术并不适合运行memcached。Memcached需要接管和控制大块的内存,如果memcached管理的内存被OS或 hypervisor交换出去,memcached的性能将大打折扣。

4)应用运行在不安全的环境中

Memcached 未提供任何安全策略,仅仅通过telnet就可以访问到memcached。如果应用运行在共享的系统上,需要着重考虑安全问题。

5)业务本身需要的是持久化数据或者说需要的应该是database的读数据

参考文章

http://blog.mimvp.com/2015/01/memcache-cache-bulk-delete-scheme/ mc的telnet操作 查看
客户端的一致性hash http://www.last.fm/user/RJ/journal/2007/04/10/rz_libketama_-_a_consistent_hashing_algo_for_memcache_clients
火丁的mc http://huoding.com/2012/12/30/205