前言
zuul 是netflix 提供的一个开源组件,致力于在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。也有很多公司使用它来作为网关的重要组成部分,碰巧今年公司的架构组决定自研一个网关产品,集动态路由,动态权限,限流配额等功能为一体,为其他部门的项目提供统一的外网调用管理,最终形成产品(这方面阿里其实已经有成熟的网关产品了,但是不太适用于个性化的配置,也没有集成权限和限流降级)。
本文主要给大家介绍了关于spring cloud zuul统一异常处理与回退的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。
一、filter中统一异常处理
其实在springcloud的edgware sr2版本中对于zuulfilter中的错误有统一的处理,但是在实际开发当中对于错误的响应方式,我想每个团队都有自己的处理规范。那么如何做到自定义的异常处理呢?
我们可以先参考一下springcloud提供的senderrorfilter:
|
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
|
/*
* copyright 2013-2015 the original author or authors.
*
* licensed under the apache license, version 2.0 (the "license");
* you may not use this file except in compliance with the license.
* you may obtain a copy of the license at
*
* http://www.apache.org/licenses/license-2.0
*
* unless required by applicable law or agreed to in writing, software
* distributed under the license is distributed on an "as is" basis,
* without warranties or conditions of any kind, either express or implied.
* see the license for the specific language governing permissions and
* limitations under the license.
*/
package org.springframework.cloud.netflix.zuul.filters.post;
import javax.servlet.requestdispatcher;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import org.apache.commons.logging.log;
import org.apache.commons.logging.logfactory;
import org.springframework.beans.factory.annotation.value;
import org.springframework.cloud.netflix.zuul.util.zuulruntimeexception;
import org.springframework.util.reflectionutils;
import org.springframework.util.stringutils;
import com.netflix.zuul.zuulfilter;
import com.netflix.zuul.context.requestcontext;
import com.netflix.zuul.exception.zuulexception;
import static org.springframework.cloud.netflix.zuul.filters.support.filterconstants.error_type;
import static org.springframework.cloud.netflix.zuul.filters.support.filterconstants.send_error_filter_order;
/**
* error {@link zuulfilter} that forwards to /error (by default) if {@link requestcontext#getthrowable()} is not null.
*
* @author spencer gibb
*/
//todo: move to error package in edgware
public class senderrorfilter extends zuulfilter {
private static final log log = logfactory.getlog(senderrorfilter.class);
protected static final string send_error_filter_ran = "senderrorfilter.ran";
@value("${error.path:/error}")
private string errorpath;
@override
public string filtertype() {
return error_type;
}
@override
public int filterorder() {
return send_error_filter_order;
}
@override
public boolean shouldfilter() {
requestcontext ctx = requestcontext.getcurrentcontext();
// only forward to errorpath if it hasn't been forwarded to already
return ctx.getthrowable() != null
&& !ctx.getboolean(send_error_filter_ran, false);
}
@override
public object run() {
try {
requestcontext ctx = requestcontext.getcurrentcontext();
zuulexception exception = findzuulexception(ctx.getthrowable());
httpservletrequest request = ctx.getrequest();
request.setattribute("javax.servlet.error.status_code", exception.nstatuscode);
log.warn("error during filtering", exception);
request.setattribute("javax.servlet.error.exception", exception);
if (stringutils.hastext(exception.errorcause)) {
request.setattribute("javax.servlet.error.message", exception.errorcause);
}
requestdispatcher dispatcher = request.getrequestdispatcher(
this.errorpath);
if (dispatcher != null) {
ctx.set(send_error_filter_ran, true);
if (!ctx.getresponse().iscommitted()) {
ctx.setresponsestatuscode(exception.nstatuscode);
dispatcher.forward(request, ctx.getresponse());
}
}
}
catch (exception ex) {
reflectionutils.rethrowruntimeexception(ex);
}
return null;
}
zuulexception findzuulexception(throwable throwable) {
if (throwable.getcause() instanceof zuulruntimeexception) {
// this was a failure initiated by one of the local filters
return (zuulexception) throwable.getcause().getcause();
}
if (throwable.getcause() instanceof zuulexception) {
// wrapped zuul exception
return (zuulexception) throwable.getcause();
}
if (throwable instanceof zuulexception) {
// exception thrown by zuul lifecycle
return (zuulexception) throwable;
}
// fallback, should never get here
return new zuulexception(throwable, httpservletresponse.sc_internal_server_error, null);
}
public void seterrorpath(string errorpath) {
this.errorpath = errorpath;
}
}
|
在这里我们可以找到几个关键点:
1)在上述代码中,我们可以发现filter已经将相关的错误信息放到request当中了:
request.setattribute("javax.servlet.error.status_code", exception.nstatuscode);
request.setattribute("javax.servlet.error.exception", exception);
request.setattribute("javax.servlet.error.message", exception.errorcause);
2)错误处理完毕后,会转发到 xxx/error的地址来处理
那么我们可以来做个试验,我们在gateway-service项目模块里,创建一个会抛出异常的filter:
|
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
|
package com.hzgj.lyrk.springcloud.gateway.server.filter;
import com.netflix.zuul.zuulfilter;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.component;
@component
@slf4j
public class myzuulfilter extends zuulfilter {
@override
public string filtertype() {
return "post";
}
@override
public int filterorder() {
return 9;
}
@override
public boolean shouldfilter() {
return true;
}
@override
public object run() {
log.info("run error test ...");
throw new runtimeexception();
// return null;
}
}
|
紧接着我们定义一个控制器,来做错误处理:
|
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
|
package com.hzgj.lyrk.springcloud.gateway.server.filter;
import org.springframework.http.httpstatus;
import org.springframework.http.responseentity;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.restcontroller;
import javax.servlet.http.httpservletrequest;
@restcontroller
public class errorhandler {
@getmapping(value = "/error")
public responseentity<errorbean> error(httpservletrequest request) {
string message = request.getattribute("javax.servlet.error.message").tostring();
errorbean errorbean = new errorbean();
errorbean.setmessage(message);
errorbean.setreason("程序出错");
return new responseentity<>(errorbean, httpstatus.bad_gateway);
}
private static class errorbean {
private string message;
private string reason;
public string getmessage() {
return message;
}
public void setmessage(string message) {
this.message = message;
}
public string getreason() {
return reason;
}
public void setreason(string reason) {
this.reason = reason;
}
}
}
|
启动项目后,我们通过网关访问一下试试:
二、关于zuul回退的问题
1、关于zuul的超时问题:
这个问题网上有很多解决方案,但是我还要贴一下源代码,请关注这个类 abstractribboncommand,在这个类里集成了hystrix与ribbon。
|
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
|

