nginx中proxy_set_header的一个坑

一个应用有多个域名同时指向,所以在程序中需要通过servername来获得访问的域名来进行不同的处理。
在测试环境没有任何问题,但是到了线上就出问题了,上网查了相关资料,解决的方法是在 location / 的设置中,增加 proxy_set_header Host $host即可。可是在我们的线上nginx中的http设置中已经设置了proxy_set_header Host $host,对于nginx的配置来说 http > server > location,所以按理说应该有这个设置了,应该有效的。
anyway,线上的问题总需要解决,于是尝试着在 location 中又增加了一遍 proxy_set_header Host $host,启动后验证,居然生效了!!
这个到底是怎么回事啊?于是到官网查文档,看到下面一段话:

Allows redefining or appending fields to the request header passed to the proxied server. 
The value can contain text, variables, and their combinations. 
These directives are inherited from the previous configuration level if and only if there are no proxy_set_header directives defined on the current level. 

注意到上面有一句话:”These directives are inherited from the previous configuration level if and only if there are no proxy_set_header directives defined on the current level.” 也就是说确实是按照 http > server > location 的顺序来传递关系,但是假如当前的级别中出现了任意一个proxy_set_header,那么在 http 或者 server 中定义的 proxy_set_header 都将失效,需要重新再定义一遍。

oh,my god,还有这么大的一个坑。

BTW:在nginx的关于proxy_set_header文档中,还说明了:
proxy_set_header Host $http_host;
proxy_set_header Host $host;
这两者的区别。
$http_host只认用户的HEADER中的HOST,假如没有那就是为空;而$host是首先认用户的HEADER中的HOST,假如为空,就用 nginx 中的 server_name 设置。所以,$host要比 $http_host 好。

error_page的正确打开方式

我们往往用nginx来总控所有upstream出现的404,500等错误页面,有以下几种方法来定义error_page,比如:

upstream someservice {      
        server 192.168.122.37:80;
}
server {
    error_page 404             /404.html;
    error_page 500 502 503 504 /50x.html;
    #error_page 404 http://www.xxx.com/notfound/page404.html;

    location / {
        proxy_pass    http://someservice;
        proxy_set_header        Host                                    $host;
        proxy_set_header        X-Forwarded-For                 $proxy_add_x_forwarded_for;
        proxy_connect_timeout   300s;
        proxy_send_timeout      300s;
        proxy_read_timeout      300s;
    }
}

但是发现在someservice中报出的404,500等错误却没有转到特定错误页面。

上述定义的error_page,只有在本nginx自己负责的本地页面报错的时候,才会触发,对于someservice的错误无动于衷。

原来是要设定一个参数:
server {
proxy_intercept_errors on;
}
这个proxy_intercept_errors参数,从字面意义上也能明白,拦截代理错误,所以明白了吧。

log_format和access_log的正确打开方式

nginx默认的log_format和access_log如下:

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;

上面的日志格式存在以下问题:
1,因为由于CDN和还有其他的nginx在请求的前面,上面的日志格式不能反映真实的用户IP。
2,缺少访问时长。
所以,我会把上面的日志格式log_format改写成:

    map $proxy_add_x_forwarded_for  $clientRealIp {
        ""  $remote_addr;
        ~^(?P<firstAddr>[0-9\.]+),?.*$      $firstAddr;
    }

    log_format  main  '$remote_addr:$remote_port - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$clientRealIp" "$proxy_add_x_forwarded_for" $request_time';

可是怎么都不生效,于是我查看了一下nginx的文档,其中对 log_format 的定义如下:

Syntax: log_format name [escape=default|json|none] string ...;
Default:    log_format combined "...";
Context:    http

对access_log的定义如下:

Syntax: access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
        access_log off;
Default:    access_log logs/access.log combined;
Context:    http, server, location, if in location, limit_except

在上面的access_log定义中的format,究竟是个什么鬼?原来这个format对应的就是上面log_format中的name,也就是说我们对我们自己定义的log_format取一个name(可以随便,默认是combined),然后在access_log中一定要指定这个name,否则access_log就是用了默认的combined的log_format,这样也就是你无论如何改了都没有效。

所以,对于一开始定义的那个log_format,在access_log中,应该如下定义:

access_log  logs/access.log main;

这个main也就是上面的log_format定义的name。

kvm在NAT模式下,虚拟机居然访问不了外网?

kvm在NAT模式下,发现虚拟机居然访问不了外网,这个不科学啊,从理论上讲,NAT中的虚拟机可以通过宿主机无防碍访问外网,怎么访问不了呢?

通过查找资料,网上都说设置net.ipv4.ip_forward = 1即可,但是这里我提醒一下,切记是在宿主机上修改!

过程如下:

在宿主机上(切记,是宿主机,不是虚拟机),修改 /etc/sysctl.conf 文件,在最后加上下面一行:
net.ipv4.ip_forward = 1

保存退出后,执行:sysctl -p /etc/sysctl.conf 即可。

假如这个时候,虚拟机还是不能访问外网,则将虚拟机重新启动一遍即可。

mysql的组内排序(取出最大,最小的一条记录)

mysql的组内排序(取出最大,最小的一条记录)这样的需求在工作中非常常见,到网上查找,有好多种解决办法,我经过多种比较测试,认为先排序、后分组的做法是最有效的,也是最容易理解的。

模拟一个稍微复杂点的例子来简单讲述这个过程。

查找用户的首单记录(包含用户注册日期,首单时间,首单商品)
users表:userid,registerdate
orders表:orderid,userid,orderdate,goodscode

那么sql应该如此写:

select * from (
    select t1.userid,t1.registerdate,t2.orderid,t2.orderdate,t2.goodscode
    from users t1
    left join orders t2 on t1.userid = t2.userid
    order by t2.orderdate
) t
group by t.userid
order by t.registerdate

上述的语句有几点需要说清:
1,此语句在oracle中是不行的,在mysql中,select的字段可以超出group设定的范围,这个是mysql的便捷之处,但是不是标准sql。
2,内层是排序,外层是分组,一旦内层排好序了后,外层的分组保证拿到的是按照排序好的次序,绝对不会混乱。

再谈go mod

在很早以前开始学习go的时候,我推荐给了一个朋友,我那朋友对我说:“go连个包管理都没有,到它有包管理的时候再来学”,确实,go的包管理一直受到众多人诟病的地方之一。

所以在开始的时候,出现了很多第三方针对go的包管理器,因为都不是官方的,受制于go语言本身的制约(go get等),这些包管理器都存在这个那个不足,很混乱,go vendor 和 govendor 不一样的,一个是官方的前版本,一个是第三方的补充版本,弄得很晕。

备注:查看go的环境变量,用 go env 命令。

直到go的1.11版本,官方才出了个正式的go mod(也就是go module),在使用go mod的时候,这个go环境变量非常重要:GO111MODULE,这个变量有3个取值:
on:表示所有go的工程一律采用go mod方式来管理包。
off:表示所有go的工程一律不采用go mod方式来管理包,如此还是用以前的方式。
auto:默认是这个,为空也是这个,那么假如工程中有go.mod这个文件,那么采用go mod方式,否则采用以前的方式。

在不采用 go mod 方式的时候,用 go get 下载包的时候,是下载到 $GOPATH/src 下面。
在采用go mod方式的时候,用 go get 下载包的时候,是下载到 $GOPATH/pkg/mod 下面。

我们知道,在用 go get 下载包的时候,特别慢,为了解决这个问题:
在不采用 go mod 方式的时候,用 go get 下载包,则需要自己解决代理的问题,很麻烦。
在采用go mod方式的时候,用 go get 下载包,则可以有众多的免费代理供我们选择(注意,凡是这样的代理,都是在go mod模式下面,传统的不行),我们常用 https://goproxy.cn/ 来进行代理,设置如下:
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

或者设置linux的环境变量也是可以的:
export GO111MODULE=on
export GOPROXY=https://goproxy.cn

在用 go mod 方式来管理包的时候,在工程中会遇到2个问题:
1,本地的包如何导入:这个问题大家在开发的时候肯定会遇到,在用传统模式的时候,根本不会有问题的,但是到了 go mod 下,就遇到本地的包没有办法导入,这篇文章写得太好了,大家可以去看:如何使用go module导入本地包
2,私有库的包如何导入:我们常常会用gitlab搭建我们的私有git库,搭建gitlab的时候,往往有一些不同于正规github的设定,比如用http协议没有用https协议,比如不支持ssh协议,如此在解决go mod的时候,网上的很多关于golang的gitlab支持文章都失效,我个人认为解决办法就是clone下来吧,放在$GOPATH/src下面吧,没有其他好的办法。

maven发生501, ReasonPhrase:HTTPS Required错误

最近用maven打包的时候,发生了如下的错误:
501, ReasonPhrase:HTTPS Required

这是因为在访问maven central库的时候,已经不再支持http方式的连接,必须要用https了。

在 maven 的 settings.xml 中设定如下内容即可解决:

<mirror>
      <id>central</id>
      <mirrorOf>central</mirrorOf>
      <name>central</name>
      <url>https://repo.maven.apache.org/maven2/</url>
</mirror>

pom在eclipse中出现Unknown line 1的错误

最近用eclipse开发基于springboot2的项目,导入springboot2的工程后,pom中第一行出现了Unknown的错误,把它从error中删除后,不影响其他正常使用,但是过一会又会出现这个错误,非常讨厌。

经过查找,是 m2e plugin的bug,有2个解决办法:
1,升级 m2e plugin,比较麻烦,我不选择这个。
2,在pom.xml中加入如下的配置:
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>

然后再 maven -> update projects… 一下,OK,一切搞定。

springboot的contextpath设置

一般情况下,我们构建的springboot工程不会使用contextpath的,使用contextpath的这种模式已经很老很老,而且有很多弊端。

但是,假如确实有需要用contextpath的话,springboot也提供了这种模式。

server.context-path=yourcontextpathname #这个是springboot 2.0 以前的写法

server.servlet.context-path=yourcontextpathname #这个是springboot 2.0 以后的写法

假如使用了 dubbox,并暴露了 rest,那么在启动的时候,别忘记了也要修改 dubbox 定义的 contextpath 路径,否则会误以为上述的修改没有作用导致启动报错,我就是犯过这样的错误,导致花了不少时间查找资料。

vue-element-admin中发生:request body发送到后台处理时报错:流EOF错误

公司后台采用 vue-element-admin 来进行前后端分离编程。

vue-element-admin 发送 GET 的请求不会有任何问题,但是在发送 POST 的请求,并且请求体是通过 request body 来发送的,发送到后端,后端就报错:流的EOF错误。

查了 vue-element-admin 的相关资料以及 issue,在这里找到答案:
axios封装后post方法超时,困扰了6小时

我整理一下上面链接中的解决办法:
1,找到工程下 mock/mock-server.js 文件
2,注释掉 55-58行的代码:

app.use(bodyParser.json())
  app.use(bodyParser.urlencoded({
    extended: true
  }))

3,注释掉 16 行的代码:

app[mock.type](mock.url, mock.response)

4,在刚才16行,加上如下的代码:

app[mock.type](mock.url, bodyParser.json(), bodyParser.urlencoded({
      extended: true
    }), mock.response)

严厉吐槽一下,vue-element-admin 到处是坑。