⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/m0_37556444/article/details/82812816 「tinysakurac」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

1. 发现问题

最近学习spring cloud,想要配合github提供的webhook功能实现线上刷新的功能(听上去很酷炫有没有)。踩了一通spring boot2.0的坑之后(主要就是2.0之后改变了配置刷新接口),总算可以通过手动发送/actuator/bus-refresh请求来实现配置的动态刷新,然而新的问题又来了,集成webhook后(需要进行内网穿透,推荐使用natapp),GitHub在进行post请求的同时默认会在body加上这么一串载荷(payload)

{
"ref": "refs/heads/master",
"before": "c34de85d9488373f09b0d7a32c03c1cc43039bfa",
"after": "7f31e5860beb93f81376bd788c46906211b0b394",
"created": false,
"deleted": false,
"forced": false,
"base_ref": null,
"compare": "https://github.com/Tinysakura/practice-config-repo/compare/c34de85d9488...7f31e5860beb",
"commits": [
{
"id": "7f31e5860beb93f81376bd788c46906211b0b394",
"tree_id": "b68b8ce368c12580bd852d8ed2eed7cbf6770075",
"distinct": true,
"message": "Update order-dev.yml",
"timestamp": "2018-09-22T11:26:29+08:00",
"url": "https://github.com/Tinysakura/practice-config-repo/commit/7f31e5860beb93f81376bd788c46906211b0b394",
"author": {
"name": "陈飞豪",
"email": "2796191719@qq.com",
"username": "Tinysakura"
},
"committer": {
"name": "GitHub",
"email": "noreply@github.com",
"username": "web-flow"
},
"added": [

],
"removed": [

],
"modified": [
"order/order-dev.yml"
]
}
],
"head_commit": {
"id": "7f31e5860beb93f81376bd788c46906211b0b394",
"tree_id": "b68b8ce368c12580bd852d8ed2eed7cbf6770075",
"distinct": true,
"message": "Update order-dev.yml",
"timestamp": "2018-09-22T11:26:29+08:00",
"url": "https://github.com/Tinysakura/practice-config-repo/commit/7f31e5860beb93f81376bd788c46906211b0b394",
"author": {
"name": "陈飞豪",
"email": "2796191719@qq.com",
"username": "Tinysakura"
},
"committer": {
"name": "GitHub",
"email": "noreply@github.com",
"username": "web-flow"
},
"added": [

],
"removed": [

],
"modified": [
"order/order-dev.yml"
]
},
"repository": {
"id": 149753569,
"node_id": "MDEwOlJlcG9zaXRvcnkxNDk3NTM1Njk=",
"name": "practice-config-repo",
"full_name": "Tinysakura/practice-config-repo",
"private": false,
"owner": {
"name": "Tinysakura",
"email": "2796191719@qq.com",
"login": "Tinysakura",
"id": 24632034,
"node_id": "MDQ6VXNlcjI0NjMyMDM0",
"avatar_url": "https://avatars0.githubusercontent.com/u/24632034?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/Tinysakura",
"html_url": "https://github.com/Tinysakura",
"followers_url": "https://api.github.com/users/Tinysakura/followers",
"following_url": "https://api.github.com/users/Tinysakura/following{/other_user}",
"gists_url": "https://api.github.com/users/Tinysakura/gists{/gist_id}",
"starred_url": "https://api.github.com/users/Tinysakura/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/Tinysakura/subscriptions",
"organizations_url": "https://api.github.com/users/Tinysakura/orgs",
"repos_url": "https://api.github.com/users/Tinysakura/repos",
"events_url": "https://api.github.com/users/Tinysakura/events{/privacy}",
"received_events_url": "https://api.github.com/users/Tinysakura/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://github.com/Tinysakura/practice-config-repo",
"description": "spring cloud实战项目的配置库",
"fork": false,
"url": "https://github.com/Tinysakura/practice-config-repo",
"forks_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/forks",
"keys_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/teams",
"hooks_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/hooks",
"issue_events_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/issues/events{/number}",
"events_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/events",
"assignees_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/assignees{/user}",
"branches_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/branches{/branch}",
"tags_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/tags",
"blobs_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/statuses/{sha}",
"languages_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/languages",
"stargazers_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/stargazers",
"contributors_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/contributors",
"subscribers_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/subscribers",
"subscription_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/subscription",
"commits_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/contents/{+path}",
"compare_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/merges",
"archive_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/downloads",
"issues_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/issues{/number}",
"pulls_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/pulls{/number}",
"milestones_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/milestones{/number}",
"notifications_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/labels{/name}",
"releases_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/releases{/id}",
"deployments_url": "https://api.github.com/repos/Tinysakura/practice-config-repo/deployments",
"created_at": 1537528905,
"updated_at": "2018-09-22T03:24:32Z",
"pushed_at": 1537586790,
"git_url": "git://github.com/Tinysakura/practice-config-repo.git",
"ssh_url": "git@github.com:Tinysakura/practice-config-repo.git",
"clone_url": "https://github.com/Tinysakura/practice-config-repo.git",
"svn_url": "https://github.com/Tinysakura/practice-config-repo",
"homepage": null,
"size": 22,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": false,
"forks_count": 0,
"mirror_url": null,
"archived": false,
"open_issues_count": 0,
"license": null,
"forks": 0,
"open_issues": 0,
"watchers": 0,
"default_branch": "master",
"stargazers": 0,
"master_branch": "master"
},
"pusher": {
"name": "Tinysakura",
"email": "2796191719@qq.com"
},
"sender": {
"login": "Tinysakura",
"id": 24632034,
"node_id": "MDQ6VXNlcjI0NjMyMDM0",
"avatar_url": "https://avatars0.githubusercontent.com/u/24632034?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/Tinysakura",
"html_url": "https://github.com/Tinysakura",
"followers_url": "https://api.github.com/users/Tinysakura/followers",
"following_url": "https://api.github.com/users/Tinysakura/following{/other_user}",
"gists_url": "https://api.github.com/users/Tinysakura/gists{/gist_id}",
"starred_url": "https://api.github.com/users/Tinysakura/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/Tinysakura/subscriptions",
"organizations_url": "https://api.github.com/users/Tinysakura/orgs",
"repos_url": "https://api.github.com/users/Tinysakura/repos",
"events_url": "https://api.github.com/users/Tinysakura/events{/privacy}",
"received_events_url": "https://api.github.com/users/Tinysakura/received_events",
"type": "User",
"site_admin": false
}
}

还特么没有取消发送载荷的功能…,于是我们的spring boot因为无法正常反序列化这串载荷而报了400错误:

Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token

于是自然而然的想到修改body为空来避免json发生转换异常,屁颠屁颠的写了个拦截器去拦截/actuator/bus-refresh节点,发现自己还是too young too naive,根本拦截不到/actuator下的节点,/error节点倒是拦截到了,但是请求都凉了好吗(怒)。不甘心放弃的楼主于是另辟蹊径,拦截器不行我们换过滤器吗。好在过滤器还是可以拦截到请求的。于是接下来就是怎么修改请求中的body了。

2. 修改body

开始修改body,萌萌的博主于是去HttpServletRequest中去寻找setInputStream方法,咦,没有。那继续去ServletRequest找,还是没有!?懵了,怎么办呢?这时候就要吹嘘一波面向对象思想了,servlet其实为我们提供了一个HttpServletRequestMapper的包装类,我们通过继承该类重写getInputStream方法返回自己构造的ServletInputStream即可达到修改request中body内容的目的。下面是代码,这里为了避免节外生枝我直接返回了一个空的body。 自定义的wrapper类

private class CustometRequestWrapper extends HttpServletRequestWrapper{
public CustometRequestWrapper(HttpServletRequest request) {
super(request);
}

@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.read() == -1 ? true:false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener readListener) {

}

@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
}

将包装类替代原始请求传递给过滤器链中的下一个

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse)servletResponse;

String url = new String(httpServletRequest.getRequestURI());

//只过滤/actuator/bus-refresh请求
if (!url.endsWith("/bus-refresh")) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}

//获取原始的body
String body = getParm(httpServletRequest);

log.info("original body:{}", body);

//使用HttpServletRequest包装原始请求达到修改post请求中body内容的目的
CustometRequestWrapper requestWrapper = new CustometRequestWrapper(httpServletRequest);

filterChain.doFilter(requestWrapper, servletResponse);
}

3. 测试

重新对配置库中的配置文件提交修改,发现请求的状态码变成了204(无返回值的成功请求),测试刷新接口也的确进行了动态刷新,可见我们的方案成功解决了问题。

4. 后话

在网上搜索解决方案时只看到了stack overflow上的一个老哥出现了和我相同的问题,三个月了还是200多浏览0回复(心疼…)。然而我的英文水平写出来也害怕他看不懂ORZ,如果以后有遇到相同问题的朋友看到我这篇博客而且对自己英文比较有自信的话,去stack overflow帮他一把吧…

文章目录
  1. 1. 1. 发现问题
  2. 2. 2. 修改body
  3. 3. 3. 测试
  4. 4. 4. 后话