薛亮的主页

  • 首页

  • 标签

  • 归档

  • 搜索

Spring Cloud使用log4j2的yml配置

发表于 2018-05-25 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

背景

自从 log4j2 发布后,就一直在使用 slf4j + log4j2 了,最近在用 Spring Cloud 搭建项目,于是就研究了一下如何在 Spring Cloud 中引入 log4j2 。之前对比了 XML 和 Properties 的方式进行配置,感觉 XML 配置更易理解,后来学习 Spring Boot 框架,由于 Spring Boot 的很多资料都使用了 YAML 语法的配置文件,使用之后感觉,相对于 XML 和 Properties 配置,YAML 语法层次感更强,看起来确实更清晰,于是这次决定使用 YAML 语法对 log4j 进行配置。

排除对 logback 的依赖

看了一下依赖关系,发现 spring-boot-starter(version = 1.4.3.RELEASE) 依赖 spring-boot-starter-logging(version = 1.4.3.RELEASE),而 spring-boot-starter-logging 使用了 slf4j + logback,因此这里首先需要排除对 logback 的依赖,在 build.gradle 中加入如下配置:

1
2
3
4
configurations {
// 在整个构建过程中排除 `ch.qos.logback:logback-classic`
all*.exclude group: 'ch.qos.logback', module: 'logback-classic'
}

添加 log4j2 依赖

Spring Boot 已经提供了 log4j2 对应的 Starter 模块 spring-boot-starter-log4j2,这里我们可以直接在 build.gradle 中引入:

1
2
3
4
5
dependencies {
// ...
compile 'org.springframework.boot:spring-boot-starter-log4j2:+'
// ...
}

log4j2.yml 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Configuration:
status: warn
name: YAMLConfig
properties:
property:
name: project.name
value: spring-cloud-demo
property:
name: pattern
value: "%d %-5p [%c]\\: %L - %m%n"
appenders:
Console:
name: STDOUT
PatternLayout:
Pattern: ${pattern}

Loggers:
Root:
level: debug
AppenderRef:
ref: STDOUT

运行项目发现会报如下异常提示信息:

1
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.

添加 com.fasterxml.jackson 依赖

查看 log4j2 关于如何 log4j2 如何查找配置文件的的介绍,log4j2 会按照
> 01. -Dlog4j.configurationFile

> 02. classpath:log4j2-test.properties
> 03. classpath:log4j2-test.yaml
> 04. classpath:log4j2-test.yml
> 05. classpath:log4j2-test.json
> 06. classpath:log4j2-test.jsn
> 07. classpath:log4j2-test.xml
> 08. classpath:log4j2.properties

> 09. classpath:log4j2.yaml
> 10. classpath:log4j2.yml
> 11. classpath:log4j2.json
> 12. classpath:log4j2.jsn
> 13. classpath:log4j2.xml

> 14. DefaultConfiguration
的顺序查找配置文件,发现并没有问题,那又是为什么呢?
查看 log4j2 文档后发现,使用 YAML 格式的配置,需要依赖 jackson 的几个包,在 build.gradle 中追加以下依赖:

1
2
3
4
5
6
7
8
dependencies {
// ...
compile 'com.fasterxml.jackson.core:jackson-databind:+'
compile 'com.fasterxml.jackson.core:jackson-core:+'
compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:+'
compile 'com.fasterxml.jackson.core:jackson-annotations:+'
}
// ...

再次启动项目,可以正常输出日志。

参考资料:jcl-over-slf4j log桥接工具简介

Spring Cloud Config Server报JSchException: UnknownHostKey异常

发表于 2018-05-22 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

问题说明

配置 Spring Cloud Config Server 时,spring.cloud.config.git.uri 用的是 ssh 的方式,结果报了如下异常,提示未知的git所在服务器的 HostKey 不在本地~/.ssh/known_hosts 列表里

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
2018-05-22 15:59:03.054 ERROR 32067 --- [nio-8085-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: Cannot clone or checkout repository] with root cause

com.jcraft.jsch.JSchException: UnknownHostKey: gitlab.com. RSA key fingerprint is b6:03:0e:39:97:9e:d0:e7:24:ce:a3:77:3e:01:42:09
at com.jcraft.jsch.Session.checkHost(Session.java:786) ~[jsch-0.1.50.jar:na]
at com.jcraft.jsch.Session.connect(Session.java:342) ~[jsch-0.1.50.jar:na]
at org.eclipse.jgit.transport.JschConfigSessionFactory.getSession(JschConfigSessionFactory.java:116) ~[org.eclipse.jgit-3.5.3.201412180710-r.jar:3.5.3.201412180710-r]
at org.eclipse.jgit.transport.SshTransport.getSession(SshTransport.java:136) ~[org.eclipse.jgit-3.5.3.201412180710-r.jar:3.5.3.201412180710-r]
at org.eclipse.jgit.transport.TransportGitSsh$SshFetchConnection.<init>(TransportGitSsh.java:262) ~[org.eclipse.jgit-3.5.3.201412180710-r.jar:3.5.3.201412180710-r]
at org.eclipse.jgit.transport.TransportGitSsh.openFetch(TransportGitSsh.java:161) ~[org.eclipse.jgit-3.5.3.201412180710-r.jar:3.5.3.201412180710-r]
at org.eclipse.jgit.transport.FetchProcess.executeImp(FetchProcess.java:136) ~[org.eclipse.jgit-3.5.3.201412180710-r.jar:3.5.3.201412180710-r]
at org.eclipse.jgit.transport.FetchProcess.execute(FetchProcess.java:122) ~[org.eclipse.jgit-3.5.3.201412180710-r.jar:3.5.3.201412180710-r]
at org.eclipse.jgit.transport.Transport.fetch(Transport.java:1115) ~[org.eclipse.jgit-3.5.3.201412180710-r.jar:3.5.3.201412180710-r]
at org.eclipse.jgit.api.FetchCommand.call(FetchCommand.java:130) ~[org.eclipse.jgit-3.5.3.201412180710-r.jar:3.5.3.201412180710-r]
at org.eclipse.jgit.api.CloneCommand.fetch(CloneCommand.java:178) ~[org.eclipse.jgit-3.5.3.201412180710-r.jar:3.5.3.201412180710-r]
at org.eclipse.jgit.api.CloneCommand.call(CloneCommand.java:125) ~[org.eclipse.jgit-3.5.3.201412180710-r.jar:3.5.3.201412180710-r]
at org.springframework.cloud.config.server.environment.JGitEnvironmentRepository.cloneToBasedir(JGitEnvironmentRepository.java:392) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at org.springframework.cloud.config.server.environment.JGitEnvironmentRepository.copyRepository(JGitEnvironmentRepository.java:364) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at org.springframework.cloud.config.server.environment.JGitEnvironmentRepository.createGitClient(JGitEnvironmentRepository.java:349) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at org.springframework.cloud.config.server.environment.JGitEnvironmentRepository.refresh(JGitEnvironmentRepository.java:165) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at org.springframework.cloud.config.server.environment.JGitEnvironmentRepository.getLocations(JGitEnvironmentRepository.java:144) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository.getLocations(MultipleJGitEnvironmentRepository.java:116) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at org.springframework.cloud.config.server.environment.AbstractScmEnvironmentRepository.findOne(AbstractScmEnvironmentRepository.java:40) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at org.springframework.cloud.config.server.environment.MultipleJGitEnvironmentRepository.findOne(MultipleJGitEnvironmentRepository.java:154) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at org.springframework.cloud.config.server.environment.EnvironmentEncryptorEnvironmentRepository.findOne(EnvironmentEncryptorEnvironmentRepository.java:53) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at org.springframework.cloud.config.server.environment.EnvironmentController.labelled(EnvironmentController.java:112) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at org.springframework.cloud.config.server.environment.EnvironmentController.defaultLabel(EnvironmentController.java:101) ~[spring-cloud-config-server-1.2.3.RELEASE.jar:1.2.3.RELEASE]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_162]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_162]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_162]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_162]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:220) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) ~[spring-webmvc-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-embed-websocket-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55) ~[spring-boot-1.4.3.RELEASE.jar:1.4.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:105) ~[spring-boot-actuator-1.4.3.RELEASE.jar:1.4.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:89) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) ~[spring-boot-actuator-1.4.3.RELEASE.jar:1.4.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.5.RELEASE.jar:4.3.5.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:108) [tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) [tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) [tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349) [tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:784) [tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:802) [tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1410) [tomcat-embed-core-8.5.6.jar:8.5.6]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.6.jar:8.5.6]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_162]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_162]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.6.jar:8.5.6]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_162]

解决方案

打开命令行,执行以下命令即可:

1
ssh-keyscan -t rsa {hostname or IP} >> ~/.ssh/known_hosts

参考:Re: Gerrit Replication problem - “Cannot replicate .. reject HostKey” - Marcel Huber - com.googlegroups.repo-discuss - MarkMail

利用正则表达式提取CSS中的背景图地址

发表于 2018-05-09 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

工作需要,用到在java中提取微信公众号文章内的背景图地址,深入了解了一下正则表达式的反向引用、环视的概念,发现非常好用,节省了很多代码,以下是最终提取CSS中背景图地址的正则表达式:

1
(background|background\-image)\s*:\s*url\s*\(\s*(('|"|&quot;)?)\s*([^\s]+)\2\s*\)

简单解释一下:

  1. (background|background\-image):首先匹配background或background-image属性
  2. \s*:第1步之后可以跟任意数量的空白字符
  3. :第2步之后必须跟一个英文冒号
  4. \s*:第3步之后可以跟任意数量的空白字符
  5. url:第4步之后必须跟一个字符串,内容为url
  6. \s*:第5步之后可以跟任意数量的空白字符
  7. \(:第6步之后必须跟小括号的左半部分,\ 是对 ( 进行转义
  8. \s*:第7步之后可以跟任意数量的空白字符
  9. (('|"|&quot;)?):第8步之后可以有或者没有' 、 " 或 &quot;
  10. \s*:第9步之后可以跟任意数量的空白字符
  11. ([^\s]+):第10步之后需要跟至少一个非空白字符,这里匹配的即为背景图的完整地址,综合考虑到背景图地址可以是绝对地址和相对地址,以及实际场景(即背景图地址可以被单引号、双引号或 &quot; 引起来,如"http://domain.com/a.jpg",但不会出现一端是单引号或双引号,另一端不是单引号或双引号的情况,如 'http://domain.com/a.jpg" 或 "http://domain.com/a.jpg&quot;),因此这里认为可以包含任意非空白字符
  12. \2:第11步之后跟一个字符,内容与第9步匹配的内容相同
  13. \s*:第12步之后可以跟任意数量的空白字符
  14. \):最后跟上小括号的的右半部分,和第7步匹配的对应

考虑到微信公众号文章内的背景图地址不可能是相对地址,这里可以对第11步进行优化,由([^\s]+) 改造为 (\/\/|http\:\/\/|https\:\/\/)([^\s]+) ,即匹配地址必须以 // 、http:// 或 https:// 开头,因此,针对提取微信公众号内文章的背景图地址,优化后的正则表达式如下:

1
(background|background\-image)\s*\:\s*url\s*\(\s*(('|"|&quot;)?)\s*(\/\/|http\:\/\/|https\:\/\/)([^\s]+)\2\s*\)

参考资料:深入理解正则表达式环视的概念与用法

sed命令使用笔记

发表于 2018-04-11 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

Linux和Mac系统的sed命令稍有差别,其中,如果需要直接修改源文件,都需要加-i 参数
Mac下必须给-i指定参数值,如-i '.bak',系统将备份源文件,备份的文件名为 源文件名 + -i后的参数值,如源文件名为filename.txt,则备份文件名为filename.txt.bak,若不需要备份源文件,则需要将 -i 参数值为空字符串,即 -i ''。
Linux下跟 -i 参数仅表示直接修改源文件。

删除指定行

  • 删除第123行
    1
    sed '123d' filename.txt

上述命令不会直接修改源文件,直接修改源文件,并将源文件备份为 filename.txt.bak 的命令如下(此处演示 -i 参数用法,下同):

1
2
sed -i '.bak' '123d' filename.txt # Mac
sed -i '123d' filename.txt # Linux (不会备份源文件)

  • 删除第99行和第123行
    1
    sed '99d;123d' filename.txt

Git 提交指定文件的部分修改

发表于 2018-02-23 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

有时同时改了两个功能A和B,并且都没有提交,这两个功能涉及到了同一个文件a,都进行了修改,但在某次提交中只希望提交功能A,所以只能提交文件a的关于功能A的部分修改,我们可以使用下面的命令对文件a进行选择性提交:

1
git add --patch <filename>

或其简短模式:

1
git add -p <filename>

执行此命令后,Git将开启一个交互式模式,它会将文件a分解成它认为合理的“块(hunk)”(文件的一部分),并和你分别确认如何处理每处的修改:

1
Stage this hunk [y,n,a,d,g,/,j,J,k,K,s,e,?]?

以下是每个选项的说明:

  • y 暂存该块
  • n 不暂存该块
  • a 暂存该块及该文件中的剩余所有块
  • d 不暂存该块及该文件中的剩余所有块
  • g 选择并跳转至指定块块
  • / 搜索与给定的正则表达式匹配的块
  • j 离开当前未定的块,处理下一个未定的块
  • J 离开当前未定的块,处理下一个块
  • k 离开当前未定的块,处理上一个未定的块
  • K 离开当前未定的块,处理上一个块
  • s 将当前的大块分成更小的块
  • e 手动编辑当前的块
  • ? 打印帮助

如果该文件尚未存储在存储库中,则可以先执行 git add -N <filename>。之后你可以继续 git add -p <filename>。

之后,您可以使用:

  • git diff --staged 检查暂存的修改是否正确
  • git reset -p 撤销暂存的错误修改
  • git commit -v 在编辑提交消息时查看您的提交

注意这与 git format-patch 命令差别很大,git format-patch 是将提交数据解析为 .patch 文件。

备查:https://git-scm.com/book/en/v2/Git-Tools-Interactive-Staging

stackoverflow原文:https://stackoverflow.com/a/1085191

微信小程序[头脑王者]刷题脚本

发表于 2018-01-16 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

Introduce

目前题库里有1001道题,大部分为[入门新手]和[起步熟手]级别的,即在这两个级别能提示答案的几率比较高

Requirements

MySQL、NPM、WIFI、手机和电脑在同一WIFI下

Usage

  1. 创建MySQL表,SQL文件:database/tounaowangzhe_structure.sql

  2. 导入共享题库,SQL文件:database/tounaowangzhe_data_xx.sql

  3. 安装anyproxy

1
npm install -g anyproxy
  1. 安装依赖
1
npm install
  1. 修改 tounaowangzhe.js 开头处的MySQL连接信息,如:host、database、user、password

  2. 生成并安装证书

生成证书:

1
anyproxy-ca

把生成的rootCA.crt给手机安装上

  1. 启动anyproxy

    1
    2
    cd tounaowangzhe
    anyproxy --rule tounaowangzhe.js
  2. 给手机WIFI配置代理,IP是电脑的IP,端口是默认的8001

Contribute

题库越丰富,提示答案的几率越高,请fork本仓库提交你自己的题库,文件名按照 database/tounaowangzhe_data_01.sql 格式,序号依次递增

获取代码:https://github.com/liangzai-cool/tounaowangzhe.git

HTTP状态码

发表于 2017-08-10 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

本文内容转载自 维基百科
本文转载时,维基百科内容最后更新时间为 2017年7月4日 (星期二) 09:37

HTTP状态码(英语:HTTP Status Code)是用以表示网页服务器超文本传输协议响应状态的3位数字代码。它由 RFC 2616 规范定义的,并得到 RFC 2518、RFC 2817、RFC 2295、RFC 2774 与 RFC 4918 等规范扩展。所有状态码的第一个数字代表了响应的五种状态之一。所示的消息短语是典型的,但是可以提供任何可读取的替代方案。 除非另有说明,状态码是HTTP / 1.1标准(RFC 7231)的一部分。

HTTP状态码的官方注册表由互联网号码分配局(Internet Assigned Numbers Authority)维护。

微软互联网信息服务 (Microsoft Internet Information Services)有时会使用额外的十进制子代码来获取更多具体信息,但是这些子代码仅出现在响应有效内容和文档中,而不是代替实际的HTTP状态代码。

1xx消息

这一类型的状态码,代表请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束。由于HTTP/1.0协议中没有定义任何1xx状态码,所以除非在某些试验条件下,服务器禁止向此类客户端发送1xx响应。这些状态码代表的响应都是信息性的,标示客户应该采取的其他行动。

  • 100 Continue
    服务器已经接收到请求头,并且客户端应继续发送请求主体(在需要发送身体的请求的情况下:例如,POST请求),或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应。要使服务器检查请求的头部,客户端必须在其初始请求中发送Expect: 100-continue作为头部,并在发送正文之前接收100 Continue状态代码。响应代码417期望失败表示请求不应继续。

  • 101 Switching Protocols
    服务器已经理解了客户端的请求,并将通过Upgrade消息头通知客户端采用不同的协议来完成这个请求。在发送完这个响应最后的空行后,服务器将会切换到在Upgrade消息头中定义的那些协议。

    只有在切换新的协议更有好处的时候才应该采取类似措施。例如,切换到新的HTTP版本(如HTTP/2)比旧版本更有优势,或者切换到一个实时且同步的协议(如WebSocket)以传送利用此类特性的资源。

  • 102 Processing(WebDAV;RFC 2518)
    WebDAV请求可能包含许多涉及文件操作的子请求,需要很长时间才能完成请求。该代码表示​​服务器已经收到并正在处理请求,但无响应可用。这样可以防止客户端超时,并假设请求丢失。

2xx成功

这一类型的状态码,代表请求已成功被服务器接收、理解、并接受。

  • 200 OK
    请求已成功,请求所希望的响应头或数据体将随此响应返回。实际的响应将取决于所使用的请求方法。在GET请求中,响应将包含与请求的资源相对应的实体。在POST请求中,响应将包含描述或操作结果的实体。

  • 201 Created
    请求已经被实现,而且有一个新的资源已经依据请求的需要而创建,且其URI已经随Location头信息返回。假如需要的资源无法及时创建的话,应当返回’202 Accepted’。

  • 202 Accepted
    服务器已接受请求,但尚未处理。最终该请求可能会也可能不会被执行,并且可能在处理发生时被禁止。

  • 203 Non-Authoritative Information(自HTTP / 1.1起)
    服务器是一个转换代理服务器(transforming proxy,例如网络加速器),以200 OK状态码为起源,但回应了原始响应的修改版本。

  • 204 No Content
    服务器成功处理了请求,没有返回任何内容。

  • 205 Reset Content
    服务器成功处理了请求,但没有返回任何内容。与204响应不同,此响应要求请求者重置文档视图。

  • 206 Partial Content(RFC 7233)
    服务器已经成功处理了部分GET请求。类似于FlashGet或者迅雷这类的HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。

  • 207 Multi-Status(WebDAV;RFC 4918)
    代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码。

  • 208 Already Reported (WebDAV;RFC 5842)
    DAV绑定的成员已经在(多状态)响应之前的部分被列举,且未被再次包含。

  • 226 IM Used (RFC 3229)
    服务器已经满足了对资源的请求,对实体请求的一个或多个实体操作的结果表示。

3xx重定向

这类状态码代表需要客户端采取进一步的操作才能完成请求。通常,这些状态码用来重定向,后续的请求地址(重定向目标)在本次响应的Location域中指明。

当且仅当后续的请求所使用的方法是GET或者HEAD时,用户浏览器才可以在没有用户介入的情况下自动提交所需要的后续请求。客户端应当自动监测无限循环重定向(例如:A→B→C→……→A或A→A),因为这会导致服务器和客户端大量不必要的资源消耗。按照HTTP/1.0版规范的建议,浏览器不应自动访问超过5次的重定向。

  • 300 Multiple Choices
    被请求的资源有一系列可供选择的回馈信息,每个都有自己特定的地址和浏览器驱动的商议信息。用户或浏览器能够自行选择一个首选的地址进行重定向。

    除非这是一个HEAD请求,否则该响应应当包括一个资源特性及地址的列表的实体,以便用户或浏览器从中选择最合适的重定向地址。这个实体的格式由Content-Type定义的格式所决定。浏览器可能根据响应的格式以及浏览器自身能力,自动作出最合适的选择。当然,RFC 2616规范并没有规定这样的自动选择该如何进行。

    如果服务器本身已经有了首选的回馈选择,那么在Location中应当指明这个回馈的URI;浏览器可能会将这个Location值作为自动重定向的地址。此外,除非额外指定,否则这个响应也是可缓存的。

  • 301 Moved Permanently
    被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个URI之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。

    新的永久性的URI应当在响应的Location域中返回。除非这是一个HEAD请求,否则响应的实体中应当包含指向新的URI的超链接及简短说明。

    如果这不是一个GET或者HEAD请求,因此浏览器禁止自动进行重定向,除非得到用户的确认,因为请求的条件可能因此发生变化。

    注意:对于某些使用HTTP/1.0协议的浏览器,当它们发送的POST请求得到了一个301响应的话,接下来的重定向请求将会变成GET方式。

  • 302 Found
    要求客户端执行临时重定向(原始描述短语为“Moved Temporarily”)。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。

    新的临时性的URI应当在响应的Location域中返回。除非这是一个HEAD请求,否则响应的实体中应当包含指向新的URI的超链接及简短说明。

    如果这不是一个GET或者HEAD请求,那么浏览器禁止自动进行重定向,除非得到用户的确认,因为请求的条件可能因此发生变化。

    注意:虽然RFC 1945和RFC 2068规范不允许客户端在重定向时改变请求的方法,但是很多现存的浏览器将302响应视作为303响应,并且使用GET方式访问在Location中规定的URI,而无视原先请求的方法。因此状态码303和307被添加了进来,用以明确服务器期待客户端进行何种反应。

  • 303 See Other
    对应当前请求的响应可以在另一个URI上被找到,当响应于POST(或PUT / DELETE)接收到响应时,客户端应该假定服务器已经收到数据,并且应该使用单独的GET消息发出重定向。这个方法的存在主要是为了允许由脚本激活的POST请求输出重定向到一个新的资源。这个新的URI不是原始资源的替代引用。同时,303响应禁止被缓存。当然,第二个请求(重定向)可能被缓存。

    新的URI应当在响应的Location域中返回。除非这是一个HEAD请求,否则响应的实体中应当包含指向新的URI的超链接及简短说明。

    注意:许多HTTP/1.1版以前的浏览器不能正确理解303状态。如果需要考虑与这些浏览器之间的互动,302状态码应该可以胜任,因为大多数的浏览器处理302响应时的方式恰恰就是上述规范要求客户端处理303响应时应当做的。

  • 304 Not Modified
    表示资源未被修改,因为请求头指定的版本If-Modified-Since或If-None-Match。在这种情况下,由于客户端仍然具有以前下载的副本,因此不需要重新传输资源。

  • 305 Use Proxy
    被请求的资源必须通过指定的代理才能被访问。Location域中将给出指定的代理所在的URI信息,接收者需要重复发送一个单独的请求,通过这个代理才能访问相应资源。只有原始服务器才能创建305响应。许多HTTP客户端(像是Mozilla和Internet Explorer)都没有正确处理这种状态代码的响应,主要是出于安全考虑。

    注意:RFC 2068中没有明确305响应是为了重定向一个单独的请求,而且只能被原始服务器建立。忽视这些限制可能导致严重的安全后果。

  • 306 Switch Proxy
    在最新版的规范中,306状态码已经不再被使用。最初是指“后续请求应使用指定的代理”。

  • 307 Temporary Redirect
    在这种情况下,请求应该与另一个URI重复,但后续的请求应仍使用原始的URI。 与302相反,当重新发出原始请求时,不允许更改请求方法。 例如,应该使用另一个POST请求来重复POST请求。

  • 308 Permanent Redirect (RFC 7538)
    请求和所有将来的请求应该使用另一个URI重复。 307和308重复302和301的行为,但不允许HTTP方法更改。 例如,将表单提交给永久重定向的资源可能会顺利进行。

4xx客户端错误

这类的状态码代表了客户端看起来可能发生了错误,妨碍了服务器的处理。除非响应的是一个HEAD请求,否则服务器就应该返回一个解释当前错误状况的实体,以及这是临时的还是永久性的状况。这些状态码适用于任何请求方法。浏览器应当向用户显示任何包含在此类错误响应中的实体内容。

如果错误发生时客户端正在传送数据,那么使用TCP的服务器实现应当仔细确保在关闭客户端与服务器之间的连接之前,客户端已经收到了包含错误信息的数据包。如果客户端在收到错误信息后继续向服务器发送数据,服务器的TCP栈将向客户端发送一个重置数据包,以清除该客户端所有还未识别的输入缓冲,以免这些数据被服务器上的应用程序读取并干扰后者。

  • 400 Bad Request
    由于明显的客户端错误(例如,格式错误的请求语法,太大的大小,无效的请求消息或欺骗性路由请求),服务器不能或不会处理该请求。

  • 401 Unauthorized(RFC 7235)
    参见:HTTP基本认证、HTTP摘要认证
    类似于403 Forbidden,401语义即“未认证”,即用户没有必要的凭据。该状态码表示当前请求需要用户验证。该响应必须包含一个适用于被请求资源的WWW-Authenticate信息头用以询问用户信息。客户端可以重复提交一个包含恰当的Authorization头信息的请求。如果当前请求已经包含了Authorization证书,那么401响应代表着服务器验证已经拒绝了那些证书。如果401响应包含了与前一个响应相同的身份验证询问,且浏览器已经至少尝试了一次验证,那么浏览器应当向用户展示响应中包含的实体信息,因为这个实体信息中可能包含了相关诊断信息。

    注意:当网站(通常是网站域名)禁止IP地址时,有些网站状态码显示的401,表示该特定地址被拒绝访问网站。

  • 402 Payment Required
    该状态码是为了将来可能的需求而预留的。该状态码最初的意图可能被用作某种形式的数字现金或在线支付方案的一部分,但几乎没有哪家服务商使用,而且这个状态码通常不被使用。如果特定开发人员已超过请求的每日限制,Google Developers API会使用此状态码。

  • 403 Forbidden
    服务器已经理解请求,但是拒绝执行它。与401响应不同的是,身份验证并不能提供任何帮助,而且这个请求也不应该被重复提交。如果这不是一个HEAD请求,而且服务器希望能够讲清楚为何请求不能被执行,那么就应该在实体内描述拒绝的原因。当然服务器也可以返回一个404响应,假如它不希望让客户端获得任何信息。

  • 404 Not Found
    请求失败,请求所希望得到的资源未被在服务器上发现,但允许用户的后续请求。没有信息能够告诉用户这个状况到底是暂时的还是永久的。假如服务器知道情况的话,应当使用410状态码来告知旧资源因为某些内部的配置机制问题,已经永久的不可用,而且没有任何可以跳转的地址。404这个状态码被广泛应用于当服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。

  • 405 Method Not Allowed
    请求行中指定的请求方法不能被用于请求相应的资源。该响应必须返回一个Allow头信息用以表示出当前资源能够接受的请求方法的列表。例如,需要通过POST呈现数据的表单上的GET请求,或只读资源上的PUT请求。

    鉴于PUT,DELETE方法会对服务器上的资源进行写操作,因而绝大部分的网页服务器都不支持或者在默认配置下不允许上述请求方法,对于此类请求均会返回405错误。

  • 406 Not Acceptable
    参见:内容协商
    请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体,该请求不可接受。

    除非这是一个HEAD请求,否则该响应就应当返回一个包含可以让用户或者浏览器从中选择最合适的实体特性以及地址列表的实体。实体的格式由Content-Type头中定义的媒体类型决定。浏览器可以根据格式及自身能力自行作出最佳选择。但是,规范中并没有定义任何作出此类自动选择的标准。

  • 407 Proxy Authentication Required(RFC 2617)
    与401响应类似,只不过客户端必须在代理服务器上进行身份验证。代理服务器必须返回一个Proxy-Authenticate用以进行身份询问。客户端可以返回一个Proxy-Authorization信息头用以验证。

  • 408 Request Timeout
    请求超时。根据HTTP规范,客户端没有在服务器预备等待的时间内完成一个请求的发送,客户端可以随时再次提交这一请求而无需进行任何更改。

  • 409 Conflict
    表示因为请求存在冲突无法处理该请求,例如多个同步更新之间的编辑冲突。

  • 410 Gone
    表示所请求的资源不再可用,将不再可用。当资源被有意地删除并且资源应被清除时,应该使用这个。在收到410状态码后,用户应停止再次请求资源。但大多数服务端不会使用此状态码,而是直接使用404状态码。

  • 411 Length Required
    服务器拒绝在没有定义Content-Length头的情况下接受请求。在添加了表明请求消息体长度的有效Content-Length头之后,客户端可以再次提交该请求。

  • 412 Precondition Failed(RFC 7232)
    服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。这个状态码允许客户端在获取资源时在请求的元信息(请求头字段数据)中设置先决条件,以此避免该请求方法被应用到其希望的内容以外的资源上。

  • 413 Request Entity Too Large(RFC 7231)
    前称“Request Entity Too Large”,表示服务器拒绝处理当前请求,因为该请求提交的实体数据大小超过了服务器愿意或者能够处理的范围。此种情况下,服务器可以关闭连接以免客户端继续发送此请求。

    如果这个状况是临时的,服务器应当返回一个Retry-After的响应头,以告知客户端可以在多少时间以后重新尝试。

  • 414 Request-URI Too Long(RFC 7231)
    前称“Request-URI Too Long”,表示请求的URI长度超过了服务器能够解释的长度,因此服务器拒绝对该请求提供服务。通常将太多数据的结果编码为GET请求的查询字符串,在这种情况下,应将其转换为POST请求。这比较少见,通常的情况包括:

    • 本应使用POST方法的表单提交变成了GET方法,导致查询字符串过长。
    • 重定向URI“黑洞”,例如每次重定向把旧的URI作为新的URI的一部分,导致在若干次重定向后URI超长。
    • 客户端正在尝试利用某些服务器中存在的安全漏洞攻击服务器。这类服务器使用固定长度的缓冲读取或操作请求的URI,当GET后的参数超过某个数值后,可能会产生缓冲区溢出,导致任意代码被执行。没有此类漏洞的服务器,应当返回414状态码。
  • 415 Unsupported Media Type
    对于当前请求的方法和所请求的资源,请求中提交的互联网媒体类型并不是服务器中所支持的格式,因此请求被拒绝。例如,客户端将图像上传格式为svg,但服务器要求图像使用上传格式为jpg。

  • 416 Requested Range Not Satisfiable(RFC 7233)
    前称“Requested Range Not Satisfiable”。客户端已经要求文件的一部分(Byte serving),但服务器不能提供该部分。例如,如果客户端要求文件的一部分超出文件尾端。

  • 417 Expectation Failed
    在请求头Expect中指定的预期内容无法被服务器满足,或者这个服务器是一个代理服显的证据证明在当前路由的下一个节点上,Expect的内容无法被满足。

  • 418 I’m a teapot(RFC 2324)
    本操作码是在1998年作为IETF的传统愚人节笑话, 在RFC 2324超文本咖啡壶控制协议’中定义的,并不需要在真实的HTTP服务器中定义。当一个控制茶壶的HTCPCP收到BREW或POST指令要求其煮咖啡时应当回传此错误。这个HTTP状态码在某些网站(包括Google.com)中用作彩蛋。

  • 421 Misdirected Request (RFC 7540)
    该请求针对的是无法产生响应的服务器(例如因为连接重用)。

  • 422 Unprocessable Entity(WebDAV;RFC 4918 )
    请求格式正确,但是由于含有语义错误,无法响应。

  • 423 Locked(WebDAV;RFC 4918)
    当前资源被锁定。

  • 424 Failed Dependency(WebDAV;RFC 4918)
    由于之前的某个请求发生的错误,导致当前请求失败,例如PROPPATCH。

  • 426 Upgrade Required(RFC 2817)
    客户端应当切换到TLS/1.0,并在HTTP/1.1 Upgrade header中给出。

  • 428 Precondition Required (RFC 6585)
    原服务器要求该请求满足一定条件。这是为了防止“‘未更新’问题,即客户端读取(GET)一个资源的状态,更改它,并将它写(PUT)回服务器,但这期间第三方已经在服务器上更改了该资源的状态,因此导致了冲突。”

  • 429 Too Many Requests (RFC 6585)
    用户在给定的时间内发送了太多的请求。旨在用于网络限速。

  • 431 Request Header Fields Too Large (RFC 6585)
    服务器不愿处理请求,因为一个或多个头字段过大。

  • 451 Unavailable For Legal Reasons
    该访问因法律的要求而被拒绝,由IETF在2015核准后新增加。

5xx服务器错误

表示服务器无法完成明显有效的请求。这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生,也有可能是服务器意识到以当前的软硬件资源无法完成对请求的处理。除非这是一个HEAD请求,否则服务器应当包含一个解释当前错误状态以及这个状况是临时的还是永久的解释信息实体。浏览器应当向用户展示任何在当前响应中被包含的实体。这些状态码适用于任何响应方法。

  • 500 Internal Server Error
    通用错误消息,服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。没有给出具体错误信息。

  • 501 Not Implemented
    服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。(例如,网络服务API的新功能)

  • 502 Bad Gateway
    作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。

  • 503 Service Unavailable
    由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是暂时的,并且将在一段时间以后恢复。如果能够预计延迟时间,那么响应中可以包含一个Retry-After头用以标明这个延迟时间。如果没有给出这个Retry-After信息,那么客户端应当以处理500响应的方式处理它。

  • 504 Gateway Timeout
    作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应。

    注意:某些代理服务器在DNS查询超时时会返回400或者500错误。

  • 505 HTTP Version Not Supported
    服务器不支持,或者拒绝支持在请求中使用的HTTP版本。这暗示着服务器不能或不愿使用与客户端相同的版本。响应中应当包含一个描述了为何版本不被支持以及服务器支持哪些协议的实体。

  • 506 Variant Also Negotiates(RFC 2295)
    由《透明内容协商协议》(RFC 2295)扩展,代表服务器存在内部配置错误,被请求的协商变元资源被配置为在透明内容协商中使用自己,因此在一个协商处理中不是一个合适的重点。

  • 507 Insufficient Storage(WebDAV;RFC 4918)
    服务器无法存储完成请求所必须的内容。这个状况被认为是临时的。

  • 508 Loop Detected (WebDAV;RFC 5842)
    服务器在处理请求时陷入死循环。 (可代替 208状态码)

  • 510 Not Extended(RFC 2774)
    获取资源所需要的策略并没有被满足。

  • 511 Network Authentication Required (RFC 6585)
    客户端需要进行身份验证才能获得网络访问权限,旨在限制用户群访问特定网络。(例如连接WiFi热点时的强制网络门户)

对于RSA密钥使用openssl和java

发表于 2017-08-07 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

使用openssl生成一个新的密钥对

您首先使用 genrsa OpenSSL中的工具生成私钥:

1
openssl genrsa -out privatekey.pem 2048

这将创建一个具有2048位长度的新的RSA私钥。密钥存储在文件中 privatekey.pem,它是“PEM”格式。PEM格式本质上是DER编码结构的base64编码变体。您可以查看文件,它应该以“BEGIN RSA PRIVATE KEY”标题开头,并以“END RSA PRIVATE KEY”页尾结尾:

1
2
3
4
head -2 privatekey.pem; tail -1 privatekey.pem 
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAth6P/MXUGL1y69Ao9THV16taSeUWnM4FQpmHP0yMDS3hB4V0
-----END RSA PRIVATE KEY-----

现在我们需要开始这个文件。虽然这似乎只是私钥,公钥似乎丢失了,但并不是:这个私钥格式包含了重建公钥数据的所有信息。

提取公钥

我们将使用的OpenSSL的第二个工具是rsa。这允许对密钥文件进行一些转换。

1
openssl rsa -in privatekey.pem -out publickey.pem -pubout

如果我们看生成文件 publickey.pem,我们看到,也是PEM格式。页眉和页脚行分别是“BEGIN PUBLIC KEY”和“END PUBLIC KEY”:

1
2
3
4
head -2 publickey.pem; tail -1 publickey.pem 
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAth6P/MXUGL1y69Ao9THV
-----END PUBLIC KEY-----

现在我们有明文密钥文件可用。您可以分发公钥文件,以允许对方加密一些数据,同时保留私钥。请注意,私钥文件未加密,必须以某种方式保护(如文件权限等)。

了解关键文件结构

Java本身不能直接加载在上述步骤中生成的PEM文件。然而,PEM文件实际上只是用附加的页眉和页脚编码的“DER”格式基础64。但是什么是“DER”格式?该手册页解释了一点:

1
2
3
4
5
6
7
8
-inform DER|NET|PEM
This specifies the input format. The DER option uses an ASN1 DER encoded form
compatible with the PKCS#1 RSAPrivateKey or SubjectPublicKeyInfo format.
The PEM form is the default format: it consists of the DER format base64
encoded with additional header and footer lines. On input PKCS#8 format
private keys are also accepted. The NET form is a format is described
in the NOTES section.
>

所以有标准的PKCS#1定义了结构RSAPrivateKey和SubjectPublicKeyInfo。该标准也作为RFC 3447出版。附录A.1.2中描述了交换私钥的建议:

1
2
3
4
5
6
7
8
9
10
11
12
13
 RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
>

你可以看到两件事情:结构基本上是数字列表。私钥结构包含模数,这就是您可以从此私钥文件中提取公钥的原因。

公钥结构 SubjectPublicKeyInfo 如附录A.1.1所述:

1
2
3
4
5
 RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e
}
>

您可以使用OpenSSL以“人类可读”格式显示此信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
openssl rsa -in privatekey.pem -text
Private-Key: (2048 bit)
modulus:
00:b6:1e:8f:fc:c5:d4:18:bd:72:eb:d0:28:f5:31:
...
publicExponent: 65537 (0x10001)
privateExponent:
00:a9:f4:cb:9a:b1:63:c5:d2:c6:b4:9a:86:1e:8c:
... It will display all the fields. The same is possible with the public key:

openssl rsa -in publickey.pem -text -pubin
Public-Key: (2048 bit)
Modulus:
00:b6:1e:8f:fc:c5:d4:18:bd:72:eb:d0:28:f5:31:
...

将密钥文件转换为Java(公共密钥)

普通Java能够理解公钥格式。但是,它不能直接读取PEM文件,但可以理解DER编码。解决方案是,首先使用Base64对文件进行解码,然后通过Java进行解析。这是一个这样做的代码段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static PublicKey loadPublicKey() throws Exception {
String publicKeyPEM = FileUtils.readFileToString(new File("publickey.pem"), StandardCharsets.UTF_8);

// strip of header, footer, newlines, whitespaces
publicKeyPEM = publicKeyPEM
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");

// decode to get the binary DER representation
byte[] publicKeyDER = Base64.getDecoder().decode(publicKeyPEM);

KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyDER));
return publicKey;
}

它用于 X509EncodedKeySpec 加载公钥,这只是推荐的 SubjectPublicKeyInfo 实现。

注意:我使用 Apache的commons-io库 FileUtils。其他一切都包含在标准的Java8 JDK中。

转换私钥用于Java

不幸的是,我们不能使用完全相同的技巧来进行私钥。Java具有私钥的编码密钥规范:PKCS8EncodedKeySpec - 但是,它实现了“PKCS#8”而不是我们使用的“PKCS#1”。幸运的是,OpenSSL还包含一个这种格式的转换器:

1
openssl pkcs8 -in privatekey.pem -topk8 -nocrypt -out privatekey-pkcs8.pem

如果您检查生成的文件,您将再次看到PEM格式,但现在标题为“BEGIN PRIVATE KEY”:

1
2
3
4
head -2 privatekey-pkcs8.pem; tail -1 privatekey-pkcs8.pem
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2Ho/8xdQYvXLr
-----END PRIVATE KEY-----

请注意,这个私钥文件也没有加密(nocrypt),并且必须保持安全。
该格式在 RFC 5208 和 第5节中 的结构中 描述:

1
2
3
4
5
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] IMPLICIT Attributes OPTIONAL }

它再次被DER编码,它实际上只是 RSAPrivateKey 在 privateKey 字段中从上面包装结构。但是,此格式允许使用密码加密私钥(我们在此示例中不使用)。

现在我们也可以在Java中加载私钥:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static PrivateKey loadPrivateKey() throws Exception {
String privateKeyPEM = FileUtils.readFileToString(new File("privatekey-pkcs8.pem"), StandardCharsets.UTF_8);

// strip of header, footer, newlines, whitespaces
privateKeyPEM = privateKeyPEM
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");

// decode to get the binary DER representation
byte[] privateKeyDER = Base64.getDecoder().decode(privateKeyPEM);

KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyDER));
return privateKey;
}

使用RSA在Java中加密和解密

现在我们可以使用Java加密和解密,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
String clearText = "Sample plain text";

PublicKey publicKey = loadPublicKey();
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(clearText.getBytes(StandardCharsets.UTF_8));

PrivateKey privateKey = loadPrivateKey();
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(encrypted);

System.out.println("ClearText: " + clearText);
System.out.println("Decrypted: " + new String(decrypted, StandardCharsets.UTF_8));
System.out.println("ClearText length: " + clearText.getBytes(StandardCharsets.UTF_8).length);
System.out.println("Encrypted length: " + encrypted.length);
System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
}

输出如下所示:

1
2
3
4
5
ClearText: Sample plain text
Decrypted: Sample plain text
ClearText length: 17
Encrypted length: 256
Encrypted: riHHycTvKaDtX3SkeoZbFCW3KW3vxEIsF3wVQqOKuwAbTtWFyP6yN5essem+jTx16Ggdp6/rzS9r9Wy5O6P8JuOQAKi...

您可以看到明文已经膨胀多达256个字节。这是因为我生成了一个2048位长度的RSA密钥,这是256字节。RSA密码以块为单位加密,并在块中使用填充。我使用“PKCS1Padding”,它使用11个字节进行填充,这意味着,最多可以在一个块中加密256-11 = 245字节的普通数据。如果您有更大的数据要加密,则需要链接这些块。链接块有不同的方法:电子码本(ECB),密码块链接(CBC)。请参阅 分组密码操作模式。您还可以考虑使用混合方法,这意味着您将首先通过RSA交换AES的对称密钥,然后将此AES密钥用于要交换的较大数据。
另请参见:
mbedtls - ASN.1 DER和PEM中的关键结构
英文原文:Using openssl and java for RSA keys

Gradle 修改文件内容后导致中文乱码

发表于 2017-05-26 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

gradle version: 3.0

昨天使用 Gradle 为测试环境打包时,使用测试环境的配置信息,替换系统配置文件后,发现配置文件中的中文注释,编程乱码了,导致发布到测试环境后,jetty 报 3 字节的 UTF-8 序列的字节 3 无效 的错误。这里简单介绍一下错误原因和解决方案。

问题背景

src/main/resources/log4j2.xml 部分内容:

1
2
3
4
5
6
...
<Properties>
<!-- 项目名称 -->
<Property name="project.name">@project.name@</Property>
</Properties>
...

build.gradle 部分内容:

1
2
3
4
5
6
processResources {
// src/main/resources 下的文件中 @key@ 的内容使用 config.groovy 里对应的进行替换
from(sourceSets.main.resources.srcDirs) {
filter(ReplaceTokens, tokens: configuration.toProperties())
}
}

上面 processResources 方法会导致 log4j2.xml 中的中文注释乱码,最终导致启动报错。

解决方案

解决这个问题并不困难,直接在 processResources 方法添加一行 filteringCharset = 'UTF-8' 即可,最终的 processResource:

1
2
3
4
5
6
7
processResources {
// src/main/resources 下的文件中 @key@ 的内容使用 config.groovy 里对应的进行替换
filteringCharset = 'UTF-8'
from(sourceSets.main.resources.srcDirs) {
filter(ReplaceTokens, tokens: configuration.toProperties())
}
}

Jetty 9 增加项目部署目录

发表于 2017-05-23 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

Jetty version = jetty-9.3.12.v20160915 - 15 September 2016

  • 在 jetty 下新建一个目录 webapps-dev 用来发布开发版本的项目

  • 找到 jetty/etc/jetty-deploy.xml 配置文件,修改第 42 ~ 49 行:
    将

    1
    2
    3
    4
    5
    6
    7
    8
    <Set name="monitoredDirName">
    <Property>
    <Name>jetty.deploy.monitoredPath</Name>
    <Default>
    <Property name="jetty.base" default="." />/<Property name="jetty.deploy.monitoredDir" deprecated="jetty.deploy.monitoredDirName" default="webapps"/>
    </Default>
    </Property>
    </Set>

注释,替换为:

1
2
3
4
5
6
7
8
9
10
<Set name="monitoredDirectories">
<Array type="java.lang.String">
<Item>
<Property name="jetty.base" default="." />/webapps
</Item>
<Item>
<Property name="jetty.base" default="." />/webapps-dev
</Item>
</Array>
</Set>

  • 启动 Jetty,从下图启动日志中红框内可以看到,已经将 jetty/webapps 和 jetty/webapps-dev 添加到 Jetty 的项目部署目录中。

    启动日志

12…4

薛亮

37 日志
45 标签
© 2015 – 2018 薛亮
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Muse v6.5.0