Nginx 配置虚拟主机

阅读本文需要安装Nginx:https://www.cnblogs.com/huangyi-427/p/9229645.html

一、什么是配置虚拟主机

就是在一台服务器启动多个网站

二、通过端口区分虚拟主机

复制一份静态页面

cd /usr/local/nginx

cp -r html html81

修改部分内容以示区分

vim /usr/local/nginx/html81/index.html

未分类

查看配置文件

more /usr/local/nginx/conf/nginx.conf
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


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

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ .php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ .php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

未分类

可以通过配置多个server来配置多个虚拟主机

添加虚拟主机 将下面配置拷贝进去(与原有的server节点同级)

vim /usr/local/nginx/conf/nginx.conf
    server {
        listen       81;
        server_name  localhost;

        location / {
            root   html81;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

进入sbin目录

cd /usr/local/nginx/sbin

开机状态刷新配置文件

./nginx -s reload

未分类

未分类

三、通过域名区分虚拟主机

域名: 域名就是网站

DNS服务器: 把域名解析为IP地址 保存的就是域名和IP的映射关系

注意: 一个域名对应一个IP地址 一个IP地址可以被多个域名绑定

这里我准备了2个域名

www.hb218.cn  www.hdcpa.cn

在阿里云上购买的 只有3-5天的使用期(可以续费) 总共花了2个大洋

买好域名之后需要在阿里云控制台 -> 云解析DNS -> 配置2个域名指向同一台nginx服务器(IP)

这里赞一下 马爸爸的阿里云平台啥都有 挺方便的

复制一份显示www.hb218.cn的静态页面

cd /usr/local/nginx

cp -r html html-hb218

修改部分内容以示区分

vim /usr/local/nginx/html-hb218/index.html

未分类

复制一份显示www.hdcpa.cn的静态页面

cd /usr/local/nginx

cp -r html html-hdcpa

修改部分内容以示区分

vim /usr/local/nginx/html-hdcpa/index.html

未分类

查看配置文件

more /usr/local/nginx/conf/nginx.conf
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


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

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ .php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ .php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

未分类

可以通过配置多个server来配置多个虚拟主机

添加虚拟主机 将下面配置拷贝进去(与原有的server节点同级)

vim /usr/local/nginx/conf/nginx.conf
    server {
        listen       80;
        server_name  www.hb218.cn;

        location / {
            root   html-hb218;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen       80;
        server_name  www.hdcpa.cn;

        location / {
            root   html-hdcpa;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

进入sbin目录

cd /usr/local/nginx/sbin

开机状态刷新配置文件

./nginx -s reload

大功告成 浏览器分别访问www.hb218.cn  www.hdcpa.cn

一个有意思的Tomcat 异常

之前有位读者留言,说了一个 Tomcat 异常的问题。
即 Tomcat 各功能正常,不影响使用,但是偶尔的在日志中会看到类似于这样的异常信息:

INFO [https-apr-8443-exec-5] org.apache.coyote.http11.Http11Processor.service Error parsing HTTP request header 
 Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level. 
 java.lang.IllegalArgumentException: Invalid character (CR or LF) found in method name 
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:443) 
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:982) 

为啥报这个呢?明明自己没做什么操作。

顺着异常信息我们往上看,首先这个提示是解析请求头出现的错误。更细节一些是解析请求头中第一行,所谓的「Request Line」的时候出了问题。

什么是「Request Line」呢? 就是HTTP 规范中指定的,以请求方法开头 再加上请求URI 等。具体看这个规范说明

未分类

这里我们的异常信息提示我们是在解析 Method name的时候出了问题。看规范里说了「The Request-Line begins with a method token」也就是有固定的东西的,不是啥都能叫一个method name。我们熟悉的GET/POST/PUT/DELETE都是这里允许的。

我们再来看 Tomcat 的源码,是如何判断这里的 Requet Line 是不是一个包含一个合法的 method name。
顺着异常的类和方法,轻车熟路,直接就能看到了。

if (parsingRequestLinePhase == 2) { 
    // 
    // Reading the method name 
    // Method name is a token 
    // 
    boolean space = false; 
    while (!space) { 
        // Read new bytes if needed 
        if (byteBuffer.position() >= byteBuffer.limit()) { 
            if (!fill(false)) // request line parsing 
                return false; 
        } 
        // Spec says method name is a token followed by a single SP but 
        // also be tolerant of multiple SP and/or HT. 
        int pos = byteBuffer.position(); 
        byte chr = byteBuffer.get(); 
        if (chr == Constants.SP || chr == Constants.HT) { 
            space = true; 
            request.method().setBytes(byteBuffer.array(), parsingRequestLineStart, 
                    pos - parsingRequestLineStart); 
        } else if (!HttpParser.isToken(chr)) { 
            byteBuffer.position(byteBuffer.position() - 1); 
            throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); 
        } 
    } 
    parsingRequestLinePhase = 3; 
} 

我们注意红色的异常就是上面产生的内容。产生这个是由于读取的byte 不是个 SP 同时下面的 isToken 也不是true导致。
那Token都有谁是怎么定义的?
这里挺有意思的,直接用一个boolean数组来存,前面我们传进来的byte,对应的是这个数组的下标。

public static boolean isToken(int c) { 
    // Fast for correct values, slower for incorrect ones 
    try { 
        return IS_TOKEN[c]; 
    } catch (ArrayIndexOutOfBoundsException ex) { 
        return false; 
    } 
} 

这里的boolean数组,初始化时有几个关联的数组一起,长度为128。

private static final boolean[] IS_CONTROL = new boolean[ARRAY_SIZE]; 
private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE]; 
private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE]; 
// Control> 0-31, 127 
if (i < 32 || i == 127) { 
    IS_CONTROL[i] = true; 
} 
// Separator 
if (    i == '(' || i == ')' || i == '<' || i == '>'  || i == '@'  || 
        i == ',' || i == ';' || i == ':' || i == '\' || i == '"' || 
        i == '/' || i == '[' || i == ']' || i == '?'  || i == '='  || 
        i == '{' || i == '}' || i == ' ' || i == 't') { 
    IS_SEPARATOR[i] = true; 
} 

// Token: Anything 0-127 that is not a control and not a separator 
if (!IS_CONTROL[i] && !IS_SEPARATOR[i] && i < 128) { 
    IS_TOKEN[i] = true; 
} 

所以这里token的定义明确了,非控制字符,非分隔符,ascii 码小于128 的都是 token。

所以问题产生原因定位了,是由于我们的请求头中传递了「非法」方法名称,导致请求不能正确处理。

我们来看一个正常的请求信息

未分类

Request Line 就是上面看到的第一行内容。 GET /a/ HTTP/1.1

那有问题的内容大概是这个样子

未分类

谁能从上面解析出来请求方法?

这时你可能会问,正常请求都好好的,你这个怎么搞的?

对。正常没问题,如果我们的Connector 是普通的此时可以响应请求,如果你一直http://localhost:port/a ,可以正常响应,此时后台收到一个https://localhost:port1/a,你要怎么响应?

要知道这两个编码大不一样。所以就出现了本文开头的问题。
如果不想走寻常路,可以自己写个Socket ,连到 Tomcat Server上,发个不合法的请求,大概也是一个样子。

那出现了这类问题怎么排查呢? 别忘了 Tomcat 提供了一系列有用的 Valve ,其中一个查看请求的叫AccessLogValve(阀门(Valve)常打开,快发请求过来 | Tomcat的AccessLogValve介绍)

在 Log里可以查看每个到达的请求来源IP,请求协议,响应状态,请求方法等。但是如果上面的异常产生时,请求方法这类有问题的内容也是拿不到的,此时的response status 是400 。但通过IP我们能看到是谁在一直请求。如果判断是非法请求后,可以再增加我们的过滤Valve,直接将其设置为Deny就OK了。

详解:配置启用NGINX状态页面

Nginx是一款免费的开源,高性能,可靠,可扩展且可完全扩展的Web服务器,负载均衡器和反向代理软件。 它有一个简单和易于理解的配置语言。 它还支持多种静态模块(自第一个版本开始就存在于Nginx中)和动态模块 (在1.9.11版本中引入 )。

Nginx中的一个重要模块是ngx_http_stub_status_module模块,它通过“ 状态页面 ”提供对基本Nginx状态信息的访问。 它显示活动客户端连接总数,接受和处理的总数,请求总数以及读取,写入和等待连接数等信息。

在大多数Linux发行版上, Nginx版本随ngx_http_stub_status_module启用。 您可以使用以下命令检查模块是否已启用。

# nginx -V 2>&1 | grep -o with-http_stub_status_module

检查Nginx状态模块

如果在终端中看到–with-http_stub_status_module作为输出,则表示状态模块已启用。 如果上述命令没有返回任何输出,则需要使用-with-http_stub_status_module作为配置参数从源代码编译NGINX ,如图所示。

# wget http://nginx.org/download/nginx-1.13.12.tar.gz
# tar xfz nginx-1.13.12.tar.gz
# cd nginx-1.13.12/
# ./configure --with-http_stub_status_module
# make
# make install

在验证模块之后,您还需要在NGINX配置文件/etc/nginx/nginx.conf中启用stub_status模块,以便为该模块设置一个本地可访问的URL(例如http://www.example.com/nginx_status )状态页面。

location /nginx_status {
stub_status;
allow 127.0.0.1;    #only allow requests from localhost
deny all;       #deny all other hosts   
}

启用Nginx状态页面

确保将127.0.0.1替换为服务器的IP地址,并确保只有您可访问此页面。

更改配置后,请确保检查nginx配置是否有任何错误,并使用以下命令重新启动nginx服务以实现最近的更改。

# nginx -t
# nginx -s reload 

检查Nginx配置

重新加载nginx服务器后,现在您可以使用curl程序访问下面的URL中的Nginx状态页面来查看您的指标。

# curl http://127.0.0.1/nginx_status
OR
# curl http://www.example.com/nginx_status

检查Nginx状态页面

重要说明 : ngx_http_stub_status_module模块已被Nginx 1.13.0版本中的ngx_http_api_module模块取代。

nginx配置location总结及rewrite规则写法

1. location正则写法

一个示例:

location  = / {
  # 精确匹配 / ,主机名后面不能带任何字符串
  [ configuration A ]
}

location  / {
  # 因为所有的地址都以 / 开头,所以这条规则将匹配到所有请求
  # 但是正则和最长字符串会优先匹配
  [ configuration B ]
}

location /documents/ {
  # 匹配任何以 /documents/ 开头的地址,匹配符合以后,还要继续往下搜索
  # 只有后面的正则表达式没有匹配到时,这一条才会采用这一条
  [ configuration C ]
}

location ~ /documents/Abc {
  # 匹配任何以 /documents/Abc 开头的地址,匹配符合以后,还要继续往下搜索
  # 只有后面的正则表达式没有匹配到时,这一条才会采用这一条
  [ configuration CC ]
}

location ^~ /images/ {
  # 匹配任何以 /images/ 开头的地址,匹配符合以后,停止往下搜索正则,采用这一条。
  [ configuration D ]
}

location ~* .(gif|jpg|jpeg)$ {
  # 匹配所有以 gif,jpg或jpeg 结尾的请求
  # 然而,所有请求 /images/ 下的图片会被 config D 处理,因为 ^~ 到达不了这一条正则
  [ configuration E ]
}

location /images/ {
  # 字符匹配到 /images/,继续往下,会发现 ^~ 存在
  [ configuration F ]
}

location /images/abc {
  # 最长字符匹配到 /images/abc,继续往下,会发现 ^~ 存在
  # F与G的放置顺序是没有关系的
  [ configuration G ]
}

location ~ /images/abc/ {
  # 只有去掉 config D 才有效:先最长匹配 config G 开头的地址,继续往下搜索,匹配到这一条正则,采用
    [ configuration H ]
}

location ~* /js/.*/.js
  • 已=开头表示精确匹配
    如 A 中只匹配根目录结尾的请求,后面不能带任何字符串。
  • ^~ 开头表示uri以某个常规字符串开头,不是正则匹配
  • ~ 开头表示区分大小写的正则匹配;
  • ~* 开头表示不区分大小写的正则匹配
  • / 通用匹配, 如果没有其它匹配,任何请求都会匹配到

顺序 no优先级:

(location =) > (location 完整路径) > (location ^~ 路径) > (location ~,~* 正则顺序) > (location 部分起始路径) > (/)

上面的匹配结果
按照上面的location写法,以下的匹配示例成立:

  • / -> config A
    精确完全匹配,即使/index.html也匹配不了

  • /downloads/download.html -> config B
    匹配B以后,往下没有任何匹配,采用B

  • /images/1.gif -> configuration D
    匹配到F,往下匹配到D,停止往下

  • /images/abc/def -> config D
    最长匹配到G,往下匹配D,停止往下
    你可以看到 任何以/images/开头的都会匹配到D并停止,FG写在这里是没有任何意义的,H是永远轮不到的,这里只是为了说明匹配顺序

  • /documents/document.html -> config C
    匹配到C,往下没有任何匹配,采用C

  • /documents/1.jpg -> configuration E
    匹配到C,往下正则匹配到E

  • /documents/Abc.jpg -> config CC
    最长匹配到C,往下正则顺序匹配到CC,不会往下到E

实际使用建议

所以实际使用中,个人觉得至少有三个匹配规则定义,如下:

#直接匹配网站根,通过域名访问网站首页比较频繁,使用这个会加速处理,官网如是说。
#这里是直接转发给后端应用服务器了,也可以是一个静态首页
# 第一个必选规则
location = / {
    proxy_pass http://tomcat:8080/index
}
# 第二个必选规则是处理静态文件请求,这是nginx作为http服务器的强项
# 有两种配置模式,目录匹配或后缀匹配,任选其一或搭配使用
location ^~ /static/ {
    root /webroot/static/;
}
location ~* .(gif|jpg|jpeg|png|css|js|ico)$ {
    root /webroot/res/;
}
#第三个规则就是通用规则,用来转发动态请求到后端应用服务器
#非静态文件请求就默认是动态请求,自己根据实际把握
#毕竟目前的一些框架的流行,带.php,.jsp后缀的情况很少了
location / {
    proxy_pass http://tomcat:8080/
}

http://tengine.taobao.org/boo…
http://nginx.org/en/docs/http…

2. Rewrite规则

rewrite功能就是,使用nginx提供的全局变量或自己设置的变量,结合正则表达式和标志位实现url重写以及重定向。rewrite只能放在server{},location{},if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用,例如 http://seanlook.com/a/we/index.php?id=1&u=str 只对/a/we/index.php重写。语法rewrite regex replacement [flag];

如果相对域名或参数字符串起作用,可以使用全局变量匹配,也可以使用proxy_pass反向代理。

表明看rewrite和location功能有点像,都能实现跳转,主要区别在于rewrite是在同一域名内更改获取资源的路径,而location是对一类路径做控制访问或反向代理,可以proxy_pass到其他机器。很多情况下rewrite也会写在location里,它们的执行顺序是:

  1. 执行server块的rewrite指令
  2. 执行location匹配
  3. 执行选定的location中的rewrite指令

如果其中某步URI被重写,则重新循环执行1-3,直到找到真实存在的文件;循环超过10次,则返回500 Internal Server Error错误。

2.1 flag标志位

  • last : 相当于Apache的[L]标记,表示完成rewrite
  • break : 停止执行当前虚拟主机的后续rewrite指令集
  • redirect : 返回302临时重定向,地址栏会显示跳转后的地址
  • permanent : 返回301永久重定向,地址栏会显示跳转后的地址

因为301和302不能简单的只返回状态码,还必须有重定向的URL,这就是return指令无法返回301,302的原因了。这里 last 和 break 区别有点难以理解:

  1. last一般写在server和if中,而break一般使用在location中
  2. last不终止重写后的url匹配,即新的url会再从server走一遍匹配流程,而break终止重写后的匹配
  3. break和last都能组织继续执行后面的rewrite指令

2.2 if指令与全局变量

if判断指令
语法为if(condition){…},对给定的条件condition进行判断。如果为真,大括号内的rewrite指令将被执行,if条件(conditon)可以是如下任何内容:

  • 当表达式只是一个变量时,如果值为空或任何以0开头的字符串都会当做false
  • 直接比较变量和内容时,使用=或!=
  • ~正则表达式匹配,~*不区分大小写的匹配,!~区分大小写的不匹配

-f!-f用来判断是否存在文件
-d!-d用来判断是否存在目录
-e!-e用来判断是否存在文件或目录
-x!-x用来判断文件是否可执行

例如:

if ($http_user_agent ~ MSIE) {
    rewrite ^(.*)$ /msie/$1 break;
} //如果UA包含"MSIE",rewrite请求到/msid/目录下

if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
    set $id $1;
 } //如果cookie匹配正则,设置变量$id等于正则引用部分

if ($request_method = POST) {
    return 405;
} //如果提交方法为POST,则返回状态405(Method not allowed)。return不能返回301,302

if ($slow) {
    limit_rate 10k;
} //限速,$slow可以通过 set 指令设置

if (!-f $request_filename){
    break;
    proxy_pass  http://127.0.0.1;
} //如果请求的文件名不存在,则反向代理到localhost 。这里的break也是停止rewrite检查

if ($args ~ post=140){
    rewrite ^ http://example.com/ permanent;
} //如果query string中包含"post=140",永久重定向到example.com

location ~* .(gif|jpg|png|swf|flv)$ {
    valid_referers none blocked www.jefflei.com www.leizhenfang.com;
    if ($invalid_referer) {
        return 404;
    } //防盗链
}

全局变量

下面是可以用作if判断的全局变量

  • $args : #这个变量等于请求行中的参数,同$query_string
  • $content_length : 请求头中的Content-length字段。
  • $content_type : 请求头中的Content-Type字段。
  • $document_root : 当前请求在root指令中指定的值。
  • $host : 请求主机头字段,否则为服务器名称。
  • $http_user_agent : 客户端agent信息
  • $http_cookie : 客户端cookie信息
  • $limit_rate : 这个变量可以限制连接速率。
  • $request_method : 客户端请求的动作,通常为GET或POST。
  • $remote_addr : 客户端的IP地址。
  • $remote_port : 客户端的端口。
  • $remote_user : 已经经过Auth Basic Module验证的用户名。
  • $request_filename : 当前请求的文件路径,由root或alias指令与URI请求生成。
  • $scheme : HTTP方法(如http,https)。
  • $server_protocol : 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
  • $server_addr : 服务器地址,在完成一次系统调用后可以确定这个值。
  • $server_name : 服务器名称。
  • $server_port : 请求到达服务器的端口号。
  • $request_uri : 包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。
  • $uri : 不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。
  • $document_uri : 与$uri相同。

例:http://localhost:88/test1/test2/test.php

$host:localhost
$server_port:88
$request_uri:http://localhost:88/test1/tes...
$document_uri:/test1/test2/test.php
$document_root:/var/www/html
$request_filename:/var/www/html/test1/test2/test.php

2.3 常用正则

  • . : 匹配除换行符以外的任意字符
  • ? : 重复0次或1次
  • + : 重复1次或更多次
  • * : 重复0次或更多次
  • d :匹配数字
  • ^ : 匹配字符串的开始
  • $ : 匹配字符串的介绍
  • {n} : 重复n次
  • {n,} : 重复n次或更多次
  • [c] : 匹配单个字符c
  • [a-z] : 匹配a-z小写字母的任意一个

小括号()之间匹配的内容,可以在后面通过$1来引用,$2表示的是前面第二个()里的内容。正则里面容易让人困惑的是转义特殊字符。

2.4 rewrite实例

例1:

http {
    # 定义image日志格式
    log_format imagelog '[$time_local] ' $image_file ' ' $image_type ' ' $body_bytes_sent ' ' $status;
    # 开启重写日志
    rewrite_log on;

    server {
        root /home/www;

        location / {
                # 重写规则信息
                error_log logs/rewrite.log notice;
                # 注意这里要用‘’单引号引起来,避免{}
                rewrite '^/images/([a-z]{2})/([a-z0-9]{5})/(.*).(png|jpg|gif)$' /data?file=$3.$4;
                # 注意不能在上面这条规则后面加上“last”参数,否则下面的set指令不会执行
                set $image_file $3;
                set $image_type $4;
        }

        location /data {
                # 指定针对图片的日志格式,来分析图片类型和大小
                access_log logs/images.log mian;
                root /data/images;
                # 应用前面定义的变量。判断首先文件在不在,不在再判断目录在不在,如果还不在就跳转到最后一个url里
                try_files /$arg_file /image404.html;
        }
        location = /image404.html {
                # 图片不存在返回特定的信息
                return 404 "image not foundn";
        }
}

对形如/images/ef/uh7b3/test.png的请求,重写到/data?file=test.png,于是匹配到location /data,先看/data/images/test.png文件存不存在,如果存在则正常响应,如果不存在则重写tryfiles到新的image404 location,直接返回404状态码。

例2:

rewrite ^/images/(.*)_(d+)x(d+).(png|jpg|gif)$ /resizer/$1.$4?width=$2&height=$3? last;

对形如/images/bla_500x400.jpg的文件请求,重写到/resizer/bla.jpg?width=500&height=400地址,并会继续尝试匹配location。

例3:

见 ssl部分页面加密http://seanlook.com/2015/05/28/nginx-ssl/

基于CentOS 7实现的NFS

NFS

NFS(Network FileSystem,网络文件系统),最早由Sun公司所发展出来的,主要是通过网络让不同的主机、不同的操作系统,可以彼此分享个别档案,因此我们也可以简单把NFS看成是一个文件服务器。通过NFS我们的PC可以将网络端的NFS服务器分享的目录挂载到本地端的机器当中,在本地端的机器看起来,远程主机的目录就好像是自己的一个磁盘分区一样。

未分类

NFS服务器与客户端通过随机选择小于1024以下的端口来进行数据传输,而这个端口的确定需要借助RPC(Remote Procedure Call,远程过程调用)协议的协助。RPC最主要的功能就是在指定每个NFS服务所对应的port number,并且回报给客户端,让客户端可以连结到正确的端口上去。当我们启动NFS服务时会随机取用数个端口,并主动向RPC注册,因此RPC可以知道每个端口对应的NFS,而RPC又是固定使用 port 111监听客户端的需求并回报客户端正确的端口。

未分类

  1. 客户端向服务器端的RPC(port 111)发出NFS的请求;
  2. 服务器端找到对应的已注册的NFS daemon端口后,回报给客户端;
  3. 客户端知道正确的端口后,直接与NFS daemon联机。

实现NFS

实验环境

[root@nfs ~]# lsb_release -r
Release:    7.2.1511
[root@nfs ~]# yum -y install nfs-utils
[root@nfs ~]# rpm -qi nfs-utils
Name        : nfs-utils
Epoch      : 1
Version    : 1.3.0
Release    : 0.54.el7
Architecture: x86_64
[root@nfs ~]# systemctl stop firewalld
[root@nfs ~]# setenforce 0
[root@nfs ~]# mkdir /nfs_dir
[root@nfs ~]# vim /nfs_dir/welcome
  This is NFS test file.

NFS相关配置

相关配置文件:
配置文件:/etc/exports
配置文件目录:/etc/exports.d

  NFS服务的主配置文件为/etc/exports,用于定义共享的目录以及客户端的权限,格式如下:

/PATH/TO/SOME_DIR clients1(export_options,...) clients2(export_options,...)

其中clients支持以下几种格式:

  • single host:ipv4,ipv6,FQDN
  • network:address/netmask
  • wildcards:主机名通配,例如,*.magedu.com
  • netgroups:NIS域内的主机组,@group_name
  • anonymous:使用*通配所有主机

export_options的常见参数可以分为以下两类:

  • General Options:
    • ro:客户端挂载后,其权限为只读,默认选项;
    • rw:读写权限;
    • sync:同时将数据写入到内存与硬盘中;
    • async:异步,优先将数据保存到内存,然后再写入硬盘;
    • Secure:要求请求源的端口小于1024
  • User ID Mapping:
    • root_squash:当NFS客户端使用root用户访问时,映射到NFS服务器的匿名用户;
    • no_root_squash:当NFS客户端使用root用户访问时,映射到NFS服务器的root用户;
    • all_squash:全部用户都映射为服务器端的匿名用户;
    • anonuid=UID:将客户端登录用户映射为此处指定的用户uid;
    • anongid=GID:将客户端登录用户映射为此处指定的用户gid

更多参数信息可以通过命令 man exports 查看帮助手册

配置NFS

[root@nfs ~]# vim /etc/exports
/nfs_dir 192.168.4.*(rw,sync,root_squash)
[root@nfs ~]# systemctl start nfs-server

客户端测试

#查看nfs能挂载的选项
[root@client ~]# showmount -e 192.168.4.119
Export list for 192.168.4.119:
/nfs_dir 192.168.4.*
[root@client ~]# mount -t nfs 192.168.4.119:/nfs_dir /mnt
[root@client ~]# cd /mnt/
[root@client mnt]# ls
welcome
[root@client mnt]# cat welcome 
This is NFS test file.
[root@client mnt]# touch file
touch: cannot touch ‘file’: Permission denied

可以看到无法在共享目录下创建文件,明明已经给分配了rw权限,这是因为root_squash把我们的访问权限压缩为nobody权限,自然无法对该目录进行写入操作。
对NFS的配置文件重新进行修改:

[root@nfs ~]# vim /etc/exports
/nfs_dir 192.168.4.*(rw,sync,no_root_squash)
#使用exportfs重读NFS配置,不需要重启服务
[root@nfs ~]# exportfs -rv
exporting 192.168.4.*:/nfs_dir

客户端重新测试:

[root@client mnt]# touch file
[root@client mnt]# ll
total 4
-rw-r--r--. 1 root root  0 Jun 28 14:11 file  #可以看到属主属组为root
-rw-r--r--. 1 root root 23 Jun 28 11:08 welcome
[root@client mnt]# echo 123 > file
[root@client mnt]# rm file
rm: remove regular empty file ‘file’? y  #可以删除文件
[root@client mnt]# ll
total 4
-rw-r--r--. 1 root root 23 Jun 28 11:08 welcome

很明显,将客户端访问共享文件用户映射为NFS服务器上的root是一种不安全的做法,我们可以指定客户端映射到NFS服务器的用户,配置如下:

[root@nfs ~]# useradd nfSUSEr -s /sbin/nologin 
[root@nfs ~]# id nfsuser
uid=1003(nfsuser) gid=1003(nfsuser) groups=1003(nfsuser)
[root@nfs ~]# chown -R nfsuser:nfsuser /nfs_dir/
[root@nfs ~]# vim /etc/exports
[root@nfs ~]# cat /etc/exports
/nfs_dir 192.168.4.*(rw,sync,all_squash,anonuid=1003,anongid=1003)
[root@nfs ~]# exportfs -rv
exporting 192.168.4.*:/nfs_dir

客户端进行测试:

[root@client mnt]# touch file
[root@client mnt]# ll
total 4
-rw-r--r--. 1 1003 1003  0 Jun 28 14:27 file
-rw-r--r--. 1 1003 1003 23 Jun 28 11:08 welcome
[root@client mnt]# echo 123 > file
[root@client mnt]# cat welcome 
This is NFS test file.
[root@client mnt]# rm file
rm: remove regular file ‘file’? y
[root@client mnt]# ll
total 4
-rw-r--r--. 1 1003 1003 23 Jun 28 11:08 welcome

zabbix配置

zabbix的一些常用配置

zabbix-agent

首先,安装完zabbix-web之后,想要监控系统,还需要在系统安装一个agent端:

yum -y install zabbix-agent

命令行安装即可,安装完成之后,可以在本地服务器下找到配置文件,我的是在:/etx/zabbix在:

zabbix_agentd.conf
zabbix_agentd.d

其中zabbix_agentd.d是文件夹,里面是具体的监控配置文件userparameter_mysql.conf,这个文件里面就是一系列zabbix可用的监控指标的配置,这个后面再专门写写zabbix的配置。

zabbix_get

zabbix有个工具:zabbix_get,可以用来获取监控指标,用法示例:

zabbix_get -s IP -k mysql.status[Com_insert]

上面这条命令在配置文件配置完成的情况下,可以用来获取服务器上MySQL执行的insert数量。

图像乱码问题

在第一次使用zabbix,图形显示,中文是一个个的方框,也就是乱码,这是因为zabbix的汉化不是很完全,可以手工替换字体来解决乱码问题。
首先在本地windows机器下拿到中文字体,例如楷体:simkai.ttf(文件路径:C:WindowsFonts)
然后把windows上下载完成的字体上传到zabbix服务器中,路径:/usr/share/zabbix/fonts
该目录下本来有一个系统字体,并且软链接到zabbix-web,也就是页面显示的字体,现在要做的就是用自己上传的楷体替换到系统原来的字体

[root@localhost fonts]# ll
total 11512
lrwxrwxrwx. 1 root root       33 Jun 26 15:46 graphfont.ttf -> /etc/alternatives/zabbix-web-font

首先是将系统原字体备份mv graphfont.ttf graphfont.ttf.back
然后修改配置文件/usr/share/zabbix/include/defines.inc.php
将文件中的FONT_NAME从原来的graphfont修改为simkai,一共有两处,修改完成之后页面上就可以看见乱码解决了~

WordPress架设多域名多站点教程

WordPress 3.0以上的版本支持直接开启多站点模式,这样一来,你可以在一个后台切换多个站点进行管理。多站点模式可以使用多个不同的域名绑定,避免了在同一个服务器安装多个WordPress的尴尬,管理多个网站非常方便,下面就介绍一下安装和启用多站点的操作方法。

开启网络配置

在网站根目录下的 wp-config.php 添加:

define('WP_ALLOW_MULTISITE', true);

  
配置多站点模式

  刷新网站后台,访问 工具 > 配置网络,根据自己的需要选择“子域名”或者“子目录”模式,然后点击“安装”,如果需要绑定域名,可以选择“子域名”模式。之后系统会生成两段代码,一段添加到网站根目录下的 wp-config.php 中,一段添加到网站根目录下的 .htaccess 文件中。

添加网站域名

  服务器Apache设置为泛域名解析,然后在 我的站点 – 管理网络 – 站点 里,添加新的站点,选一个子域名添加站点。

  添加好了以后,在子域名上点编辑,把里面的“站点地址(URL)”修改为指定的独立域名,然后将该独立域名DNS绑定过来,即可完成配置。

设置COOKIE

在网站根目录下的 wp-config.php 添加:

define( 'COOKIE_DOMAIN', '' );

  这步操作很重要,如果不设置的话,之后登录网站后台会提示“错误:Cookies被阻止或者您的浏览器不支持。要使用WordPress,您必须启用Cookies。”

  之后,用户就可以用一套WordPress程序来管理多个不同的站点,可以同步调整主题和插件,这些站点可以是完全不同的域名,管理起来非常方便。

Tomcat是怎样处理搜索引擎爬虫请求的?

每个置身于互联网中的站点,都需要搜索引擎的收录,以及在适时在结果中的展现,从而将信息提供给用户、读者。而搜索引擎如何才能收录我们的站点呢?

这就涉及到一个「搜索引擎的爬虫」爬取站点内容的过程。只有被搜索引擎爬过并收录的内容才有机会在特定query命中之后在结果中展现。

这些搜索引擎内容的工具,又被称为爬虫、Sprider,Web crawler 等等。我们一方面欢迎其访问站点以便收录内容,一方面又因其对于正常服务的影响头疼。毕竟 Spider 也是要占用服务器资源的, Spider 太多太频繁的资源占用,正常用户请求处理就会受到影响。所以一些站点干脆直接为搜索引擎提供了单独的服务供其访问,其他正常的用户请求走另外的服务器。

说到这里需要提一下,对于是否是 Spider 的请求识别,是通过HTTP 请求头中的User-Agent 字段来判断的,每个搜索引擎有自己的独立标识。而且通过这些内容,管理员也可以在访问日志中了解搜索引擎爬过哪些内容。

此外,在对搜索引擎的「爬取声明文件」robots.txt中,也会有类似的User-agent 描述。比如下面是taobao 的robots.txt描述

User-agent:  Baiduspider 
Allow:  /article 
Allow:  /oshtml 
Disallow:  /product/ 
Disallow:  / 

User-Agent:  Googlebot 
Allow:  /article 
Allow:  /oshtml 
Allow:  /product 
Allow:  /spu 
Allow:  /dianpu 
Allow:  /oversea 
Allow:  /list 
Disallow:  / 

User-agent:  Bingbot 
Allow:  /article 
Allow:  /oshtml 
Allow:  /product 
Allow:  /spu 
Allow:  /dianpu 
Allow:  /oversea 
Allow:  /list 
Disallow:  / 

User-Agent:  360Spider 
Allow:  /article 
Allow:  /oshtml 
Disallow:  / 

User-Agent:  Yisouspider 
Allow:  /article 
Allow:  /oshtml 
Disallow:  / 

User-Agent:  Sogouspider 
Allow:  /article 
Allow:  /oshtml 
Allow:  /product 
Disallow:  / 

User-Agent:  Yahoo!  Slurp 
Allow:  /product 
Allow:  /spu 
Allow:  /dianpu 
Allow:  /oversea 
Allow:  /list 
Disallow:  / 

我们再来看 Tomcat对于搜索引擎的请求做了什么特殊处理呢?
对于请求涉及到 Session,我们知道通过 Session,我们在服务端得以识别一个具体的用户。那 Spider 的大量请求到达后,如果访问频繁同时请求量大时,就需要创建巨大量的 Session,需要占用和消耗很多内存,这无形中占用了正常用户处理的资源。

为此, Tomcat 提供了一个 「Valve」,用于对 Spider 的请求做一些处理。

首先识别 Spider 请求,对于 Spider 请求,使其使用相同的 SessionId继续后面的请求流程,从而避免创建大量的 Session 数据。
这里需要注意,即使Spider显式的传了一个 sessionId过来,也会弃用,而是根据client Ip 来进行判断,即对于 相同的 Spider 只提供一个Session。

我们来看代码:

// If the incoming request has a valid session ID, no action is required 
if (request.getSession(false) == null) { 

    // Is this a crawler - check the UA headers 
    Enumeration<String> uaHeaders = request.getHeaders("user-agent"); 
    String uaHeader = null; 
    if (uaHeaders.hasMoreElements()) { 
        uaHeader = uaHeaders.nextElement(); 
    } 

    // If more than one UA header - assume not a bot 
    if (uaHeader != null && !uaHeaders.hasMoreElements()) { 
        if (uaPattern.matcher(uaHeader).matches()) { 
            isBot = true; 
            if (log.isDebugEnabled()) { 
                log.debug(request.hashCode() + 
                        ": Bot found. UserAgent=" + uaHeader); 
            } 
        } 
    } 

    // If this is a bot, is the session ID known? 
    if (isBot) { 
        clientIp = request.getRemoteAddr(); 
        sessionId = clientIpSessionId.get(clientIp); 
        if (sessionId != null) { 
            request.setRequestedSessionId(sessionId); // 重用session 
        } 
    } 
} 

getNext().invoke(request, response); 

if (isBot) { 
    if (sessionId == null) { 
        // Has bot just created a session, if so make a note of it 
        HttpSession s = request.getSession(false); 
        if (s != null) { 
            clientIpSessionId.put(clientIp, s.getId()); //针对Spider生成session 
            sessionIdClientIp.put(s.getId(), clientIp); 
            // #valueUnbound() will be called on session expiration 
            s.setAttribute(this.getClass().getName(), this); 
            s.setMaxInactiveInterval(sessionInactiveInterval); 

            if (log.isDebugEnabled()) { 
                log.debug(request.hashCode() + 
                        ": New bot session. SessionID=" + s.getId()); 
            } 
        } 
    } else { 
        if (log.isDebugEnabled()) { 
            log.debug(request.hashCode() + 
                    ": Bot session accessed. SessionID=" + sessionId); 
        } 
    } 
} 

判断Spider 是通过正则

private String crawlerUserAgents = 
    ".*[bB]ot.*|.*Yahoo! Slurp.*|.*Feedfetcher-Google.*"; 
// 初始化Valve的时候进行compile 
uaPattern = Pattern.compile(crawlerUserAgents); 

这样当 Spider 到达的时候就能通过 User-agent识别出来并进行特别处理从而减小受其影响。

这个 Valve的名字是:「CrawlerSessionManagerValve」,好名字一眼就能看出来作用。

其他还有问题么?我们看看,通过ClientIp来判断进行Session共用。
最近 Tomcat 做了个bug fix,原因是这种通过ClientIp的判断方式,当 Valve 配置在Engine下层,给多个Host 共用时,只能有一个Host生效。 fix之后,对于请求除ClientIp外,还有Host和 Context的限制,这些元素共同组成了 client标识,就能更大程度上共用Session。
修改内容如下:

未分类

总结下, 该Valve 通过标识识别出 Spider 请求后,给其分配一个固定的Session,从而避免大量的Session创建导致我资源占用。
默认该Valve未开启,需要在 server.xml中 增加配置开启。另外我们看上面提供的 正则 pattern,和taobao 的robots.txt对比下,你会出现并没有包含国内的这些搜索引擎的处理,这个时候怎么办呢?
在配置的时候传一下进来就OK啦,这是个public 的属性

public void setCrawlerUserAgents(String crawlerUserAgents) { 
    this.crawlerUserAgents = crawlerUserAgents; 
    if (crawlerUserAgents == null || crawlerUserAgents.length() == 0) { 
        uaPattern = null; 
    } else { 
        uaPattern = Pattern.compile(crawlerUserAgents); 
    } 
}

自用ssh命令总结

最近工作有涉及ssh相关,学习中会不断更新修改

通过cgywin,xshell,linux,堡垒机等连接ssh ,具体方法之后会补充 

  1. SSH可以连接到远程主机; 

  2. SCP后面是 /开头是本地地址, xx@xxxx: 用户名@ip地址+ / remote地址 ; 

  3. ssh -i +(直接拖动来的证书文件)+ ip ( 物理ip还是虚拟ip?)
    例如 ssh -i xxx.xxx.xxx.xxx:./ ; 

  4. 本地拷贝到远程设备:scp -i +(直接拖动来的证书)+ (带地址的本地文件,直接拖动来)
    +要移动的ip:路径(绝对路径或者相对路径)

    例如 scp -i xx/xx/id_rsa.rsa ../xx/test.lua xxx.xxx.xxx.xxx:./:./ ; 

  5. 远程主机下载到本机,将远程ip的文件和本机文件调换位置即可; 

  6. ssh连接远程主机后,可以用logcat监控当前安卓设备的操作,例如点击进入某个app,通过logcat看包名; 

  7. ssh后, getevent -l 可以查看当前鼠标在安卓设备上点击的位置(注意坐标点,和16进制需要转换);

  8. 可以在安卓手机的设置里面查看ip地址 

  9. ls:当前路径下的文件, pwd:当前目录所在路径, cat:查看文件, cgywin修改文件需要下载vim模块,
    ifconfig: 注意是if而不是ip,查看ip等信息 ,有时候scp和安装需要调整权限

  10. 可以通过ssh查看当前设备的ip等信息: 例如 /data/lxc/bin/lxc-info -n 0 

  11. 有时候ssh连接需要将证书放到cgywin的bin目录下跑一次

Mac使用ssh公钥登录Linux

ssh登录Linux通常有两种方法:用户名密码登录、用户名公钥登录;使用用户名密码登录每次都要输入密码,相当麻烦,而使用用户名公钥登录则可以避免这个问题。

创建公钥私钥文件
打开本地终端,执行 ssh-keygen 命令创建密钥对:

ssh-keygen -t rsa -C  'your [email protected]'

-t 指定密钥类型,默认即 rsa ,可以省略
-C 设置注释文字,比如你的邮箱,可以省略

生成过程中会提示输入密码两次,如果不想在使用公钥的时候输入密码,可以回车跳过;
密钥默认保存位置在 ~/.ssh 目录下,打开后会看到私钥文件 id_rsa 和公钥文件 id_rsa.pub;

未分类

复制公钥至服务器
使用 scp 命令将本地的公钥文件 id_rsa.pub 复制到需要连接的Linux服务器:

scp ~/.ssh/id_rsa.pub <用户名>@<ip地址>:/home/id_rsa.pub

如果修改了ssh默认连接端口的话,需要加上端口信息:

scp -P <端口号> ~/.ssh/id_rsa.pub <用户名>@<ip地址>:/home/id_rsa.pub

把公钥追加到服务器ssh认证文件中:

cat /home/id_rsa.pub >> ~/.ssh/authorized_keys

未分类

这时候在本地终端中使用用户名和ip登录就不需要密码了:

ssh <用户名>@<ip>

未分类

如果修改了ssh默认连接端口的话,需要加上端口信息:

ssh -p <端口号> <用户名>@<ip地址>

未分类

配置快捷登录
即使不用输入密码,这样每次登录还要输入用户名ip端口信息还是有点麻烦,我们可以配置ssh快捷登录更方便的登录Linux;
在本地 ~/.ssh/config 配置文件中添加ssh服务器信息,格式:

Host            alias            #自定义别名
HostName        hostname         #替换为你的ssh服务器ip或domain
Port            port             #ssh服务器端口,默认为22
User            user             #ssh服务器用户名
IdentityFile    ~/.ssh/id_rsa    #第一个步骤生成的公钥文件对应的私钥文件

未分类

这时候就可以使用配置文件中自定义的别名来登录了:

未分类