Winse Blog

走走停停都是风景, 熙熙攘攘都向最好, 忙忙碌碌都为明朝, 何畏之.

jarsperreports生成PDF中文问题

上个月弄JarsperReport报表,当时就有中文不能显示的问题。由于比较忙一直没有处理(能显示English基本能满足要求),最近又遇到决定把它倒腾倒腾解决掉。

这里简单罗列下步骤,清楚怎么弄了其实非常简单。

  1. 添加Jasperreport需要的字体(注意不是系统字体哦): Window - Preferences - Jaspersoft Studio - Fonts - Add按钮
  2. 编辑弹出框Font Family:Family Name简单易记的就行(相当于唯一标识),添加Normal/Bold字体TTF的(微软雅黑是ttc的可以网上找工具转成ttf),PDF Details选择Identity-H、以及Embed this font in PDF document.
  3. 在报表jrxml中设置需要显示中文的文字字体为 微软雅黑 (刚刚设置的名称),重新编译生成jasper文件。预览导出PDF已经可以正常显示中文了。
  4. 导出字体为jar:回到Preferences字体配置页面,点击Export按钮把字体导出为jar。
  5. 把上面导出的jar放到应用的lib目录下。

完成上面的步骤PDF就能展示中文了。

字体比较大,可以直接把jar加启动tomcat的classpath: Debug Configurations - Apache Tomcat - Classpath - User Entries 。

–END

Nginx Https With Tomcat Http

昨天配置了HTTPS了,nginx https代理访问应用的http登录页也确实没有问题的。一切都是那么的完美,然而今天一早测试的姐姐告诉我:登录失败报错400 Bad Request The plain HTTP request was sent to HTTPS port.

  • nginx 1.10.2
  • tomcat 8.0.38

初步定位问题

然后想起有看到过红薯蜀黍的 https://www.oschina.net/question/12_213459 如下:(注:最终版在最后,如果有兴趣可以看看心路历程)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# tomcat server.xml
  <Service name="Catalina">
    <Connector port="9000" protocol="HTTP/1.1"
               connectionTimeout="20000"
               URIEncoding="UTF-8" 
               redirectPort="14443"
               scheme="https" 
               proxyPort="14443" />
  ...

    <Engine name="Catalina" defaultHost="localhost">

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

      <Valve className="org.apache.catalina.valves.RemoteIpValve"
                remoteIpHeader="x-forwarded-for"
                remoteIpProxiesHeader="x-forwarded-by"
                protocolHeader="x-forwarded-proto"
            />
  
# nginx 
    server {
        listen       14443 ssl;
        server_name localhost;

        ssl on;
        ssl_certificate      nginx.crt;
        ssl_certificate_key  nginx.key;

        ssl_session_cache    shared:SSL:10m;
        ssl_session_timeout  5m;

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

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

        location /omc {
          proxy_set_header Host $http_host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto https;
          proxy_connect_timeout      240;
          proxy_send_timeout         240;
          proxy_read_timeout         240;
  
          proxy_pass http://localhost:9000; #默认连的是http的端口
          proxy_redirect off;
          #proxy_redirect https://$host/ / ;
      }
    }

登录请求确实都是https请求了,但是重定向(302)返回的https的端口丢失了(即被替换为默认的443)。查了很多资料,先弄了一个折中的处理方式,把hostname替换掉,即最后注释的那一行proxy_redirect。

查看的文章多半是http问题和多了端口的问题。我这是少端口了,但是还是有参考价值对proxy_redirect和port_in_redirect多了解一点:

还有一些没啥卵用,还带点误导性质的,但是还是得把它帖出来(蜜汁尴尬):(注:不是说人家的有错,而是说和上面的Valve结合后不行了)

关于redirectPort

翻到一篇关于Valve的文章,看不明白,感觉应该用远程调试看看为什么端口变成默认的了。

先看redirectPort,仅当http请求有安全约束才会转到端口使用SSL传输。so,redirectPort在这里没啥卵用。就当拓展知识了

1
2
redirectPort 
If this Connector is supporting non-SSL requests, and a request is received for which a matching <security-constraint> requires SSL transport, Catalina will automatically redirect the request to the port number specified here.

web.xml里面可以配置security-constraint节点

1
2
3
4
5
6
7
8
9
<security-constraint> 
<web-resource-collection> 
<web-resource-name>services</web-resource-name> 
<url-pattern>/login/*</url-pattern> 
</web-resource-collection> 
<user-data-constraint> 
<transport-guarantee>CONFIDENTIAL</transport-guarantee> 
</user-data-constraint> 
</security-constraint>

但是终究不是一种的解决问题的办法,而且怎么看怎么感觉Connector的redirectPort咋一点作用都没有呢?

Valve问题所在,解决https以及端口问题的源泉

由于是https请求,tcpdump从端口查到的数据都是看不懂的。并且不知道问题是在tomcat还是nginx,只能想着远程调试看看端口是在什么时刻被修改的。

1
2
3
4
5
6
7
8
9
10
11
[root@cu1 apache-tomcat-8.0.38]# export JPDA_ADDRESS=8000
[root@cu1 apache-tomcat-8.0.38]# bin/catalina.sh jpda run

----

# 本地pom.xml增加如下,要来查看tomcat的源码
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>8.0.38</version>
</dependency>

然后本地maven项目临时加入tomcat的包,开启VPN后,在eclipse的 org.apache.catalina.connector.ResponseFacade.sendRedirect(String) 打断点调试。然后定位到 org.apache.coyote.Request.setServerPort(int), 最终确定在 org.apache.catalina.valves.RemoteIpValve.setPorts(Request, int)

RemoteIpValve类里面的Header与nginx中配置Header对应就行的。

最终成果

注:nginx/tomcat配置https的方法,请查看前一篇文章。

注2:还有tomcat里面Header是不区分大小写的: org.apache.tomcat.util.http.MimeHeaders.getValue(String)

注3:如果配置proxyPort(而不是Valve的话)取到协议好像不对(没验证),并且配置Valve可以不影响Connector。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# nginx
    server {
        listen       14443 ssl;
        server_name localhost;

        ssl on;
        ssl_certificate      nginx.crt;
        ssl_certificate_key  nginx.key;

        ssl_session_cache    shared:SSL:10m;
        ssl_session_timeout  5m;

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

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

        location /omc {
        port_in_redirect on;

        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Port $server_port; # !!自定义port header
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_connect_timeout      240;
        proxy_send_timeout         240;
        proxy_read_timeout         240;

        proxy_pass http://localhost:9000;
        #proxy_redirect default;
        proxy_redirect off;
        #proxy_redirect https://$host/ / ;
        }

    }

# tomcat server.xml
  <Service name="Catalina">
    <Connector port="9000" protocol="HTTP/1.1"
               connectionTimeout="20000"
               URIEncoding="UTF-8"
               redirectPort="8443"
                />
  ...
    <Engine name="Catalina" defaultHost="localhost">
  ...
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
          ...
            <Valve className="org.apache.catalina.valves.RemoteIpValve"
                  portHeader="x-forwarded-port"
                  remoteIpHeader="x-forwarded-for"
                  proxiesHeader="x-forwarded-by"
                  protocolHeader="x-forwarded-proto"
            />

福利

nginx https 代理tomcat https: 其实就是和http代理一样,很简单。记得删掉上面的removeipvalve。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# tomcat server.xml
<Connector
           protocol="org.apache.coyote.http11.Http11AprProtocol"
           port="9443" maxThreads="200"
           scheme="https" secure="true" SSLEnabled="true"
           SSLCertificateFile="/home/cu/tools/apache-tomcat-8.0.38/conf/nginx.crt"
           SSLCertificateKeyFile="/home/cu/tools/apache-tomcat-8.0.38/conf/nginx.key"
           SSLVerifyClient="optional" SSLProtocol="TLSv1+TLSv1.1+TLSv1.2"/>

# nginx
location /omc {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout      240;
proxy_send_timeout         240;
proxy_read_timeout         240;

proxy_redirect off;
proxy_pass https://localhost:9443;
}

nginx websockt:

1
2
3
4
5
6
7
8
    location /omc/webSocket {
            proxy_pass http://localhost:8888/omc/webSocket;
            proxy_redirect off;

            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
    }

文件大小:

1
2
3
4
proxy_read_timeout 86400;
proxy_send_timeout 86400;

client_max_body_size 1024m;

–END

Nginx配置https

很早前弄过https,当时只是玩玩实际工作中并没有用到。现在业务需要得配置https。欠的债总是要还的,使用puppet的时刻默认全部信任不走证书认证。这里为了安装tomcat的https还是绕不过去。硬着头皮整吧。

编译

1
2
3
4
5
6
7
8
[... nginx-1.10.2]$ tar zxvf zlib-1.2.8.tar.gz 
[... nginx-1.10.2]$ tar zxvf pcre-8.36.tar.gz 

[... nginx-1.10.2]$ mv zlib-1.2.8 src/zlib
[... nginx-1.10.2]$ mv pcre-8.36 src/pcre

[... nginx-1.10.2]$ ./configure --prefix=/home/hadoop/nginx --with-http_ssl_module --with-pcre=src/pcre --with-zlib=src/zlib
[... nginx-1.10.2]$ make && make install

生成证书

先整一个能访问的https,再通过本地CA来进行授权(本地浏览器安装这个CA),最后处理chrome sha-1的问题。

  • 自己签发(无CA)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# RSA密钥
[hadoop@cu1 conf]$ openssl genrsa -des3 -out server.key 1024
Generating RSA private key, 1024 bit long modulus
...........++++++
................++++++
e is 65537 (0x10001)
Enter pass phrase for server.key:
Verifying - Enter pass phrase for server.key:
# 拷贝一个不需要输入密码的密钥文件
[hadoop@cu1 conf]$ cp server.key server.key.org
[hadoop@cu1 conf]$ openssl rsa -in server.key.org -out server.key
Enter pass phrase for server.key.org:
writing RSA key

# 需要提交给 SSL 认证机构的(也叫作:生成一个证书请求)
[hadoop@cu1 conf]$ openssl req -new -key server.key -out server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:GD
Locality Name (eg, city) [Default City]:GZ
Organization Name (eg, company) [Default Company Ltd]:Eshore
Organizational Unit Name (eg, section) []:CU
Common Name (eg, your name or your server's hostname) []:cu.esw.cn
Email Address []:ca@esw.cn

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

# 认证机构颁发(自己签发证书)
[hadoop@cu1 conf]$ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
Signature ok
subject=/C=CN/ST=Guangdong/L=Guangzhou/O=Eshore/OU=CU/CN=cu1
Getting Private key
  • 本地CA授权 & SHA2(推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
[root@cu1 apache-tomcat-8.0.38]# ll /etc/pki/CA
total 16
drwxr-xr-x. 2 root root 4096 Mar  1  2016 certs 存放CA签署(颁发)过的数字证书(证书备份目录)
drwxr-xr-x. 2 root root 4096 Mar  1  2016 crl 吊销的证书
drwxr-xr-x. 2 root root 4096 Mar  1  2016 newcerts
drwx------. 2 root root 4096 Mar  1  2016 private 用于存放CA的私钥
[root@cu1 apache-tomcat-8.0.38]# ll /etc/pki/tls/
total 24
lrwxrwxrwx  1 root root    19 May 22  2015 cert.pem -> certs/ca-bundle.crt
drwxr-xr-x. 2 root root  4096 Mar 22  2016 certs
drwxr-xr-x. 2 root root  4096 Mar 22  2016 misc
-rw-r--r--  1 root root 10906 Feb 24  2016 openssl.cnf openssl的CA主配置文件
drwxr-xr-x. 2 root root  4096 Mar  1  2016 private 证书密钥存放目录

# 修改配置(一部分为默认值,一部分与sha2有关)
[root@cu1 pki]# pwd
/etc/pki
[root@cu1 pki]# vi tls/openssl.cnf 
修改一些参数,后面的是修改后的
[root@cu1 pki]# diff /home/hadoop/openssl.cnf tls/openssl.cnf 
50c50
< certificate   = $dir/cacert.pem       # The CA certificate
---
> certificate   = $dir/ca.crt   # The CA certificate
55c55
< private_key   = $dir/private/cakey.pem# The private key
---
> private_key   = $dir/private/ca.key # The private key
74c74
< default_crl_days= 30                  # how long before next CRL
---
> default_crl_days= 365                 # how long before next CRL
86,87c86,87
< stateOrProvinceName   = match
< organizationName      = match
---
> stateOrProvinceName   = optional
> organizationName      = optional
107c107
< default_md            = sha1
---
> default_md            = sha256
126c126
< # req_extensions = v3_req # The extensions to add to a certificate request
---
> req_extensions = v3_req # The extensions to add to a certificate request
130c130
< countryName_default           = XX
---
> countryName_default           = CN
135c135
< #stateOrProvinceName_default  = Default Province
---
> #stateOrProvinceName_default  = GD

# CA证书
清理原来的旧文件
[root@cu1 pki]# cd CA
[root@cu1 CA]# rm -rf index.txt* serial*
[root@cu1 CA]# rm cacert.pem private/cakey.pem 
初始化
[root@cu1 CA]# touch index.txt serial
[root@cu1 CA]# echo 01 > serial 

[root@cu1 CA]# openssl genrsa -out private/ca.key 2048
Generating RSA private key, 2048 bit long modulus
.........................................................................+++
...........................+++
e is 65537 (0x10001)

[root@cu1 CA]# chmod 600 private/ca.key 

[root@cu1 CA]# openssl req -new -x509 -key private/ca.key -out ca.crt
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [CN]:CN
State or Province Name (full name) []:GD
Locality Name (eg, city) [Default City]:GZ
Organization Name (eg, company) [Default Company Ltd]:Eshore
Organizational Unit Name (eg, section) []:CU   
Common Name (eg, your name or your server's hostname) []:esw.cn
Email Address []:ca@esw.cn

# 应用端证书的新建
[root@cu1 conf]# openssl genrsa -out nginx.key 2048
Generating RSA private key, 2048 bit long modulus
...................................................................+++
.....+++
e is 65537 (0x10001)
[root@cu1 conf]# 

[root@cu1 conf]# openssl req -new -key nginx.key -out nginx.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [CN]:CN
State or Province Name (full name) []:GD
Locality Name (eg, city) [Default City]:GZ
Organization Name (eg, company) [Default Company Ltd]:Eshore
Organizational Unit Name (eg, section) []:Cu
Common Name (eg, your name or your server's hostname) []:cu.esw.cn
Email Address []:cu@esw.cn

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

查看是否为sha-2
[root@cu1 conf]# openssl req -in nginx.csr -text | grep sha256

# 应用端证书颁发 方式二选一
* 默认的方式,但不是sha-2。把生成的证书请求csr文件发到CA服务器上,在CA上执行:
签发过程 其实默认使用了 配置文件中指定的ca.crt和ca.key这两个文件.
[root@cu1 conf]# openssl ca -in nginx.csr -out nginx.crt
Using configuration from /etc/pki/tls/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
        Serial Number: 1 (0x1)
        Validity
            Not Before: Jan 19 07:24:24 2017 GMT
            Not After : Jan 19 07:24:24 2018 GMT
        Subject:
            countryName               = CN
            stateOrProvinceName       = GD
            organizationName          = Eshore
            organizationalUnitName    = Cu
            commonName                = cu.esw.cn
            emailAddress              = cu@esw.cn
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:FALSE
            Netscape Comment: 
                OpenSSL Generated Certificate
            X509v3 Subject Key Identifier: 
                7B:3D:E8:18:D9:77:20:8F:A2:76:7F:6C:F2:01:65:49:3F:92:1A:7F
            X509v3 Authority Key Identifier: 
                keyid:5F:8C:1E:3D:F7:A1:86:82:22:F5:88:12:36:16:82:49:B6:9C:84:F1

Certificate is to be certified until Jan 19 07:24:24 2018 GMT (365 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
[root@cu1 conf]# 

* sha-2签名算法
http://www.ibm.com/support/knowledgecenter/zh/SSPMR3_9.0.0/com.ibm.tivoli.tem.doc_9.0/SUA_9.0/com.ibm.license.mgmt.doc/security/t_generate_certificate_CA.html
[root@cu1 conf]# openssl x509 -req -days 365 -in nginx.csr -CA /etc/pki/CA/ca.crt -CAkey /etc/pki/CA/private/ca.key -CAserial /etc/pki/CA/serial -sha256 -out nginx.crt 
Signature ok
subject=/C=CN/ST=GD/L=GZ/O=Eshore/OU=Cu/CN=cu.esw.cn/emailAddress=cu@esw.cn
Getting CA Private Key

生成key和csr请求可以一条命令搞定( 这是后期增加的,和本文的一些配置有出处 ):

1
2
3
https://www.digicert.com/easy-csr/openssl.htm

openssl req -new -newkey rsa:2048 -nodes -out star_winse_com.csr -keyout star_winse_com.key -subj "/C=CN/ST=GD/L=GZ/O=winse/CN=*.winse.com"

nginx配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
server {
    listen       14443 ssl;
    server_name  cu.esw.cn;

    ssl on;
    ssl_certificate      nginx.crt;
    ssl_certificate_key  nginx.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;
    }

    location /omc {
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        Host $http_host;

        proxy_pass http://localhost:9000;
    }

}

MAKR

同时,为了统一,你可以把这三个文件都移动到 /etc/ssl/private/ 目录。 然后可以修改 Nginx 配置文件 server {
listen 80; listen [::]:80 ssl ipv6only=on;
listen 443 ssl; listen [::]:443 ssl ipv6only=on; server_name example.com; ssl on; ssl_certificate /etc/ssl/private/example_com.crt; ssl_certificate_key /etc/ssl/private/example_com.key; }

server_names barretlee.com *.barretlee.com ssl on; ssl_certificate /etc/nginx/ssl/barretlee.com.crt; ssl_certificate_key /etc/nginx/ssl/barretlee.com.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers “EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !MEDIUM”;

Add perfect forward secrecy

ssl_prefer_server_ciphers on; add_header Strict-Transport-Security “max-age=31536000; includeSubdomains”;

同时,如果是全站 HTTPS 并且不考虑 HTTP 的话,可以加入 HSTS 告诉你的浏览器本 网站全站加密,并且强制用 HTTPS 访问 add_header Strict-Transport-Security max-age=63072000; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; 同时也可以单独开一个 Nginx 配置,把 HTTP 的访问请求都用 301 跳转到 HTTPS

浏览器访问HTTP的请求重定向到HTTPS If they come here using HTTP, bounce them to the correct scheme error_page 497 https://$host:$server_port$request_uri;

curl访问:正常访问HTTPS需要把CA证书内容加入到ca-bundle.crt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@hadoop-master2 nginx]# curl https://www.winse.com
curl: (60) Peer certificate cannot be authenticated with known CA certificates
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.
 
[root@hadoop-master2 CA]# cp /etc/pki/tls/certs/ca-bundle.crt{,.bak}
[root@hadoop-master2 CA]# cat /etc/pki/CA/ca.crt >> /etc/pki/tls/certs/ca-bundle.crt
[root@hadoop-master2 CA]# 
[root@hadoop-master2 CA]# curl https://www.winse.com
curl: (51) SSL: certificate subject name 'winse.com' does not match target host name 'www.winse.com'
[root@hadoop-master2 CA]# curl https://winse.com
...

tomcat配置https

直接使用nginx的key和crt,需要安装APR.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@cu1 apache-tomcat-8.0.38]# yum install tomcat-native
[root@cu1 apache-tomcat-8.0.38]# less conf/server.xml 
<Connector
           protocol="org.apache.coyote.http11.Http11AprProtocol"
           port="9443" maxThreads="200"
           scheme="https" secure="true" SSLEnabled="true"
           SSLCertificateFile="/home/cu/tools/apache-tomcat-8.0.38/conf/nginx.crt"
           SSLCertificateKeyFile="/home/cu/tools/apache-tomcat-8.0.38/conf/nginx.key"
           SSLVerifyClient="optional" SSLProtocol="TLSv1+TLSv1.1+TLSv1.2"/>
           
#另一种keystore
<Connector
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           port="8443" maxThreads="200" SSLEnabled="true"
           scheme="https" secure="true" 
           keystoreFile="/root/.keystore" keystorePass="changeit"
           clientAuth="false" sslProtocol="TLS" />
           

本地CURL访问

把/etc/pki/CA目录下的ca.crt拷贝本地,安装到受信任的根证书颁发机构目录下面。然后https访问即可。

参考

–END

elasticsearch5安装Head插件

新版本ES5和原来的有写不同,head等一些插件不能直接安装,需要单独起一个程序然后通过ajax的方式获取数据。

下载 elasticsearch-5.1.1 ,修改配置 network.host: cu-ud1 ,然后启动服务 bin/elasticsearch -d 。

Head插件安装相对比较麻烦。在windows一些插件安装不了,现在能上外网的Linux下载依赖,然后把全部的包复制到生产环境。最后配置并启动head服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[hadoop@cu2 elasticsearch-head]$ npm install

[ud@cu-ud1 ~]$ xz -d node-v6.9.2-linux-x64.tar.xz 
[ud@cu-ud1 ~]$ tar xvf node-v6.9.2-linux-x64.tar 
[ud@cu-ud1 node-v6.9.2-linux-x64]$ vi ~/.bash_profile 
...
NODE_HOME=/home/ud/node-v6.9.2-linux-x64
PATH=$NODE_HOME/bin:$PATH
export PATH

[ud@cu-ud1 ~]$ tar zxvf elasticsearch-head-standalone.tar.gz 

[ud@cu-ud1 elasticsearch-5.1.1]$ vi config/elasticsearch.yml 
...
http.cors.enabled: true
http.cors.allow-origin: "*"

[ud@cu-ud1 elasticsearch-head]$ node_modules/grunt/bin/grunt server

然后浏览器访问9100端口即可。

–END

spark2.0 + kafka0.10.1订阅多个但只读了一个分区

同事在使用Spark-Kafka-Streaming的时刻遇到只能读取一个分区的情况,最后他找到问题所在。这里记录下,说白了就是Spark-2.0.0默认是用Kafka-0.10.0.1,自己换程序版本有风险!

问题的关键点

  • Kafka-0.10.1.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
org/apache/kafka/clients/consumer/KafkaConsumer.java
    private void updateFetchPositions(Set<TopicPartition> partitions) {
        // lookup any positions for partitions which are awaiting reset (which may be the
        // case if the user called seekToBeginning or seekToEnd. We do this check first to
        // avoid an unnecessary lookup of committed offsets (which typically occurs when
        // the user is manually assigning partitions and managing their own offsets).
        fetcher.resetOffsetsIfNeeded(partitions);

        if (!subscriptions.hasAllFetchPositions()) {
            // if we still don't have offsets for all partitions, then we should either seek
            // to the last committed position or reset using the auto reset policy

            // first refresh commits for all assigned partitions
            coordinator.refreshCommittedOffsetsIfNeeded();

            // then do any offset lookups in case some positions are not known
            fetcher.updateFetchPositions(partitions);
        }
    }
  • Kafka-0.10.0.1
1
2
3
4
5
6
7
8
org.apache.kafka.clients.consumer.KafkaConsumer#updateFetchPositions
    private void updateFetchPositions(Set<TopicPartition> partitions) {
        // refresh commits for all assigned partitions
        coordinator.refreshCommittedOffsetsIfNeeded();

        // then do any offset lookups in case some positions are not known
        fetcher.updateFetchPositions(partitions);
    }

问题描述以及说明

当订阅同一个主题的多个分区时,每次SparkStreaming会获取每次处理的Offset。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
org.apache.spark.streaming.kafka010.DirectKafkaInputDStream#latestOffsets
  protected def latestOffsets(): Map[TopicPartition, Long] = {
    val c = consumer
    c.poll(0)
    val parts = c.assignment().asScala

    // make sure new partitions are reflected in currentOffsets
    val newPartitions = parts.diff(currentOffsets.keySet)
    // position for new partitions determined by auto.offset.reset if no commit
    currentOffsets = currentOffsets ++ newPartitions.map(tp => tp -> c.position(tp)).toMap
    // don't want to consume messages, so pause
    c.pause(newPartitions.asJava)
    // find latest available offsets
    c.seekToEnd(currentOffsets.keySet.asJava)
    parts.map(tp => tp -> c.position(tp)).toMap
  }
  
  override def compute(validTime: Time): Option[KafkaRDD[K, V]] = {
    val untilOffsets = clamp(latestOffsets())
    val offsetRanges = untilOffsets.map { case (tp, uo) =>
      val fo = currentOffsets(tp)
      OffsetRange(tp.topic, tp.partition, fo, uo)
    }
    val rdd = new KafkaRDD[K, V](
      context.sparkContext, executorKafkaParams, offsetRanges.toArray, getPreferredHosts, true)
... 

如果使用kafka-0.10.1.0时,seekToEnd会重置当前客户端分区实例的position为null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
org.apache.kafka.clients.consumer.KafkaConsumer#seekToEnd
    public void seekToEnd(Collection<TopicPartition> partitions) {
        acquire();
        try {
            Collection<TopicPartition> parts = partitions.size() == 0 ? this.subscriptions.assignedPartitions() : partitions;
            for (TopicPartition tp : parts) {
                log.debug("Seeking to end of partition {}", tp);
                subscriptions.needOffsetReset(tp, OffsetResetStrategy.LATEST);
            }
        } finally {
            release();
        }
    }
org.apache.kafka.clients.consumer.internals.SubscriptionState#needOffsetReset(TopicPartition, OffsetResetStrategy)
    public void needOffsetReset(TopicPartition partition, OffsetResetStrategy offsetResetStrategy) {
        assignedState(partition).awaitReset(offsetResetStrategy);
    } 
org.apache.kafka.clients.consumer.internals.SubscriptionState.TopicPartitionState#awaitReset
        private void awaitReset(OffsetResetStrategy strategy) {
            this.resetStrategy = strategy;
            this.position = null;
        }

此时再调用position一个个分区的获取最新位置信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
org.apache.kafka.clients.consumer.KafkaConsumer#position
    public long position(TopicPartition partition) {
        acquire();
        try {
            if (!this.subscriptions.isAssigned(partition))
                throw new IllegalArgumentException("You can only check the position for partitions assigned to this consumer.");
            Long offset = this.subscriptions.position(partition);
            if (offset == null) {
                updateFetchPositions(Collections.singleton(partition));
                offset = this.subscriptions.position(partition);
            }
            return offset;
        } finally {
            release();
        }
    }

    private void updateFetchPositions(Set<TopicPartition> partitions) {
        // lookup any positions for partitions which are awaiting reset (which may be the
        // case if the user called seekToBeginning or seekToEnd. We do this check first to
        // avoid an unnecessary lookup of committed offsets (which typically occurs when
        // the user is manually assigning partitions and managing their own offsets).
        fetcher.resetOffsetsIfNeeded(partitions);

        if (!subscriptions.hasAllFetchPositions()) {
            // if we still don't have offsets for all partitions, then we should either seek
            // to the last committed position or reset using the auto reset policy

            // first refresh commits for all assigned partitions
            coordinator.refreshCommittedOffsetsIfNeeded();

            // then do any offset lookups in case some positions are not known
            fetcher.updateFetchPositions(partitions);
        }
    } 

org.apache.kafka.clients.consumer.internals.Fetcher#resetOffsetsIfNeeded
    public void resetOffsetsIfNeeded(Set<TopicPartition> partitions) {
        for (TopicPartition tp : partitions) {
            // TODO: If there are several offsets to reset, we could submit offset requests in parallel
            if (subscriptions.isAssigned(tp) && subscriptions.isOffsetResetNeeded(tp))
                resetOffset(tp);
        }
    }
org.apache.kafka.clients.consumer.internals.SubscriptionState.TopicPartitionState#seek
        private void seek(long offset) {
            this.position = offset;
            this.resetStrategy = null;
        } 

新版本KafkaConsumer先更新位置,最终调用seek设置position以及重置resetStrategy。

但是后面又额外多了一个判断!!检测所有的分区,只要有一个有问题就重新获取position,最对有问题啊!尽管后面又调用updateFetchPositions但是环境已经变了啊!!导致多个分区的情况下只能读取一个分区的数据。

问题找到了,直接客户端用旧的就行了。

–END