# nut
**Repository Path**: null_357_9907/nut
## Basic Information
- **Project Name**: nut
- **Description**: 一个Java编写自带MVC框架的HTTP服务器,支持Sun和Netty两种方式启动,并支持了Spring MVC的部分注解,支持Spring的部分注解(Component、Autowired、Qualifier)实现IOC,用来学习HTTP及Web框架相关知识,未使用第三方依赖,自定义Request、Response、Cookie、Session及ServerContext,基于JDK1.8开发。
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2022-07-23
- **Last Updated**: 2022-07-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Nut
一个Java编写自带MVC框架的HTTP服务器,支持Sun和Netty两种方式启动,并支持了Spring MVC的部分注解,支持Spring的部分注解(Component、Autowired、Qualifier)实现IOC,用来学习HTTP及Web框架相关知识,未使用第三方依赖,未实现标准的Servlet规范,自定义Request、Response、Cookie、Session及ServerContext,基于JDK1.8开发。
# 特性
1. 学习简单的Http网络知识,了解如何处理HTTP请求;
2. 学习如何开发web处理框架;
3. 支持Sun和Netty(如classPath下存在Netty相关依赖则自动以Netty方式启动)方式启动服务;
4. 支持HTTP与HTTPS(jks格式证书);
5. 已实现Spring MVC部分注解(如`@Controller`、`@ResuestBody`等注解);
6. 已实现Spring IOC部分注解(如`@Component`、`@Service`、`@Repository`、`@Autowired`、`@Qualifier`等注解)实现IOC;
7. 可作为简单的静态文件服务器使用;
8. 可开启脚本接口支持,通过编写配置文件快速搭建测试接口;
9. 支持多种视图表达式(可自行扩展);
10. 支持多种函数表达式(可自行扩展);
11. 支持表达式嵌套使用;
12. 支持脚本文件监听,脚本文件修改后自动刷新接口。
# 使用
可直接使用`java -jar nut-1.0.1-SNAPSHOT.jar`进行启动,如需配置参数,需要在参数名前加`-Dserver.`进行配置,如配置端口号`-Dserver.port=8080`,目前如下参数可以配置:
| 参数名 | 默认值 | 说明 |
|:--------------------|:------|------------|
| env | 无 | 环境变量 |
| port | 8080 | 端口号 |
| secure | false | 是否以HTTPS启动 |
| jks_path | 无 | jks证书库路径 |
| jk_password | 无 | jks证书库密码 |
| static_dir | 无 | 静态资源的磁盘目录 |
| static_path | 无 | 静态资源的访问路径 |
| enable_script | false | 是否开启脚本 |
| script_path | 无 | 脚本资源的磁盘路径 |
| enable_script_watch | false | 是否开启脚本文件监听 |
> 如开启 enable_script_watch 后,将会自动监听脚本文件,脚本文件更新后无需重启服务即可重新加载脚本文件。如修改访问路径,自动加载配置后,旧访问路径仅仅保留之前的接口信息,但仍然可以访问,新接口将保持修改后的最新信息。
## 静态文件web服务
作为静态文件web服务使用时,可通过命令行参数进行配置:
`java -jar nut-1.0.1-SNAPSHOT.jar -Dserver.static_dir={静态资源的磁盘目录} -Dserver.static_path={静态资源的访问路径}`
例如:
`java -jar nut-1.0.1-SNAPSHOT.jar -Dserver.static_dir=/root/static -Dserver.static_path=/static`
启动成功后可通过`http://localhost:8080/static/*` 进行访问(*为static_dir下的静态文件)
## 自动接口脚本
通过该功能可以在本地以编写配置文件的方式对外提供HTTP服务,而无需进行代码开发,用于测试或临时接口提供时使用。可通过命令行参数进行配置:
`java -jar nut-1.0.1-SNAPSHOT.jar -Dserver.enable_script=true -Dserver.script_path={脚本资源的磁盘路径(多个脚本可以使用逗号(,)进行分割)}`
例如:
`java -jar nut-1.0.1-SNAPSHOT.jar -Dserver.enable_script=true -Dserver.script_path=/root/script.txt`
### 脚本编写方式
脚本以`[`开始,以`]`结束,在中括号包裹的范围内需要配置`path`、`method`、`response_header`、`response_cookie`、`body`五个参数,其中`path`、`method`、`body`是必填项目,`response_header`、`response_cookie`是非必填项目。
| 参数名 | 参数说明 | 备注 |
|:--------------------|:---------|--------------------------------------------------|
| path | 访问路径 | 如:`/path` 多个路径可以用逗号(,)拼接,如:`/path1,/path2` |
| method | 访问方法 | 可选值为HTTP规范中支持的方法如:`get`、`post`等,如需要配置所有方法可设置为`*` |
| response_header(可选) | 响应头 | 向请求方的响应中加入指定的响应头 |
| response_cookie(可选) | 响应cookie | 向请求方的响应中加入指定的cookie |
| body | 响应体 | 向请求方响应指定内容 |
接口可配置成单行,如:`[path=/path1;method=get;response_header=[name=value];response_cookie=[name=value];body=hello world;]`
也可配置多行,在多行中分号(;)可选,如:
```
[
path=/path2
method=get
response_header=[name=value]
response_cookie=[name=value]
body=hello world
]
```
在`body`参数中,如果响应的内容过多可以采用多行文本方式进行配置,以```开始和结束,和markdown语法一致。
```
[
path=/path3
method=get
response_header=[name=value]
response_cookie=[name=value]
body=%text(```hello
world
```)
]
```
> 脚本只处理`[表达式]`之间的内容,其他格式的内容均不会影响脚本的解析,所以非`[表达式]`的内容均会被忽略,这些内容都可以作为注释存在在脚本文件中,例如:
```
# 单行脚本注释
[path=/path1;method=get;response_header=[name=value];response_cookie=[name=value];body=hello world;]
// 多行脚本注释
[
path=/path2
method=get
response_header=[name=value]
response_cookie=[name=value]
body=hello world
]
```
### 视图表达式
在`body`中,支持视图表达式以响应不同的媒体类型,视图表达式以`%`开始,后接视图名称,后以`()`包裹的内容为视图表达式参数,如:`%text(hello wolrd)`,目前支持的视图表达式如下:
| 视图表达式名称 | 表达式说明 |
|:--------|:-------|
| text | 纯文本视图 |
| html | html视图 |
| xml | xml视图 |
| json | json视图 |
| file | 文件视图 |
| image | 图片视图 |
如:
`[path=/path4;method=get;body=%html(
hello,world
);]`,则可以提供html视图给请求方。
`[path=/path5;method=get;body=%file(/root/record.txt);]`,则可以将本地`/root/record.txt`文件里的内容提供请求方。
`[path=/path6;method=get;body=%image(/root/sun.jpg);]`,则可以将本地`/root/sun.jpg`图片提供给请求方。
**其中视图表达式可以进行嵌套处理**, 例如:
`[path=/path7;method=get;body=%html(%file(/root/hello.html));]`,则可以将本地的`/root/hello.html`以HTML媒体类型的方式响应给请求方。
### 函数表达式
在`response_header`、`response_cookie` 和 `body` 中支持使用函数表达式,函数表达式以`$`开始,后接函数名称,后以`()`包裹的内容为函数参数,如:`$upper(abc)`,目前支持的函数表达式如下:
#### WEB函数
| 函数表达式 | 函数表达式名称 | 参数名 | 函数表达式说明 |
|:-------------|:---------|:-------|:--------------------------------------------------|
| cookie | cookie函数 | (name) | 获取请求中的cookie值,如$cookie(name)获取请求中的名称为name的cookie值 |
| header | 请求头函数 | (name) | 获取请求中的header值,如$header(name)获取请求中的名称为header的请求头值 |
| path | 请求路径函数 | 无参数 | 获取请求的路径 |
| method | 请求方法函数 | 无参数 | 获取请求的方法 |
| param | 请求参数函数 | (name) | 获取请求参数,在url中的参数或以表单方式提交的参数,name:参数名 |
| pathVariable | 路径参数函数 | (name) | 获取路径请求参数,在url中的路径参数,如:`/user/{id}`,name:参数名 |
| requestBody | 请求体函数 | 无参数 | 以字符串形式获取请求体内容 |
#### 通用函数
| 函数表达式 | 函数表达式名称 | 参数名 | 函数表达式说明 |
|:-----------|:---------|:------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| fupper | 首字母大写函数 | (str) | 将给定的字符串转换为以首字母大写的形式返回,str:字符串 |
| upper | 字母大写函数 | (str) | 将给定的字符串转换为大写的形式返回,str:字符串 |
| lower | 字母小写函数 | (str) | 将给定的字符串转换为小写的形式返回,str:字符串 |
| random | 随机函数 | [a, b] 或 (number, number(可选)) | 获取随机数,直接传入数组,将在数组中随机一个元素返回。
只传入一个`number`则返回`0-number`之间的一个随机数。
传入两个`number`则返回两个`number`之间的随机数支持整数和小数 |
| subString | 字符串截取函数 | (str, separator(可选), int(可选), int) | 字符串截取,str:要截取的字符串,
(str, int)截取从指定的索引开始(包括)的字符串;
(str, int, int)截取从指定索引开始(包括)从指定索引结束(不包括)的字符串;
(str, separator, int, int) 在给定separator标志后指定索引开始(包括)从指定索引结束(不包括)的字符串; |
| date | 日期函数 | (pattern(可选), startTime(可选), endTime(可选)) | 无参数时候,获取当前时间戳;
(pattern),格式化当前时间;
(startTime, endTime)从指定范围随机返回一个时间戳;
(pattern, startTime, endTime)格式化指定范围内的时间; |
| jsonObject | Json对象函数 | (json, field) | 获取json中的字段值,json:json字符串,field:字段名,可多级嵌套,如:`user.address.code` |
#### 扩展函数
| 函数表达式 | 函数表达式名称 | 参数名 | 函数表达式说明 |
|:---------------|:---------|:---------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------|
| nameGenerator | 姓名生成函数 | (surname(可选), boy(可选)) | 中文姓名生成,surname=1为复姓,surname=0或不传为单姓名,boy=1为男性,boy=0或不传为女性 |
| ipGenerator | ip生成函数 | 无 | ipv4地址随机生成 |
| phoneGenerator | 手机号生成函数 | (op(可选)) | op运营商:0 移动 1 联通 2 电信 |
| uuidGenerator | uuid生成函数 | (separator(可选)) | UUID生成,true:包含分隔符,false:不包含分隔符 |
| http | Http请求函数 | (url, method(可选), headers(可选)) | 以http请求获取指定url的响应结果
method,请求方法,支持以下方法:GET、POST、PUT、DELETE
headers,请求头,以`=`拼接,多个请求头以`&`分割,例如:name1=value1&name2=value2 |
| js | JS请求函数 | (js) | 执行JS函数,使用JDK的JS引擎运行,语法参考JDK使用 |
| valid | 验证函数 | (expression, success[可选], failure[可选]) | 验证指定的表达式是否为真,真时返回success表达式,默认`success`,假时返回failure表达式,默认`failure`。
表达式支持`&&`和 || 逻辑运算符,支持`==`、`>=`、`>`、`<=`、`<`关系运算符 |
##### 内存存储函数
| 函数表达式 | 函数表达式名称 | 参数名 | 函数表达式说明 |
|:-------|:--------|:--------------------------|:------------------------------------------------------|
| set | 保存键值函数 | (key, value, success[可选]) | 保存指定键值对,key:键,value:值,success:成功时返回的字符串,默认`success` |
| get | 获取值函数 | (key, failure[可选]) | 获取指定键对应的值,key:键,failure:指定键对应的值不存在时返回的字符串,默认`failure` |
| delete | 删除键函数 | (key, success[可选]) | 删除指定键对应的值,key:键,success:删除成功时返回的字符串,默认`success` |
> 函数表达式之间可以嵌套使用,因为函数表达式中间使用`,`作为参数分隔符,如果参数中也需要使用字符`,`,可以使用符号\`将表达式进行包裹。 例如`$valid`函数,如果返回参数是json格式,其中也包含`,`就可以这样使用:$valid(true, \`{"code": 200, "msg": "success"}\`, \`{"code": 400, "msg": "failure"}\`)
函数表达式示例:
```
# 保存用户信息示例
[
path=/user/{id}
method=post
body=%json($set(user:$pathVariable(id), `$requestBody()`,`{"code": 200, "msg": "成功"}`))
]
# 获取用户信息示例
[
path=/user/{id}
method=get
body=%json($get(user:$pathVariable(id), `{"code": 404, "msg": "用户信息不存在"}`))
]
# 以Json请求体形式进行用户登录示例
[
path=/user/login
method=post
body=%json($valid($jsonObject(`$requestBody()`, username) == user && $jsonObject(`$requestBody()`, password) == 123456, `{"code": 200, "msg": "登录成功"}`, `{"code": 400, "msg": "用户名或密码错误"}`))
]
# 以表单形式进行用户登录示例
[
path=/user/form_login
method=post
body=%json($valid($param(username) == user && $param(password) == 123456, `{"code": 200, "msg": "登录成功"}`, `{"code": 400, "msg": "用户名或密码错误"}`))
]
```
# 开发
Nut 已完整实现自定义的`Request`、`Response`、`Cookie`、`Session`及`ServerContext`,可依赖Nut开发web服务。
## Controller
Nut 使用`Controller`来处理请求,可以自定义`Controller`来处理请求,`Controller`提供了`doGet`、`doPost`、`doPut`、`doDelete`、`doOptions`、`doTrace`来处理Http请求,如果没有对应请求的方法,可以重载`doRequest`方法,该方法是请求处理的主方法。
`Controller` 的方法均包含三个参数:`request`、`response`及`execution`,`request`是用来封装请对象信息,`response`是用来处理客户端的响应,`execution`种包含了系统种的扩展信息,例如:请求转发器(Forward)、绑定上下文(BindContext)等等。
### Controller转发
使用`Controller`接口方法中的`execution`参数获取转发器(`Forward`),使用`forward`方法来从一个`Controller`跳转到另一个`Controller`。
### 重定向
使用`Controller`接口方法中的`Response`接口参数的`sendRedirect`方法,可以向客户端发送302重定向请求。
### Controller的注册
开发好的`Controller`可调用`Nut.getWebConfigure#getControllerManager`方法获取到`ControllerManager`来对`Controller`进行注册。Nut 支持两种方式的Controller注册:
1. 实现`Controller`接口,调用`ControllerManager#registerController(Controller, String...)`来注册;
2. 支持Lambda表达式注册,`RequestHandler`为支持注册的函数式接口,调用`ControllerManager#registerController(RequestHandler, Method, String...)`来注册;
也可以使用`@Controller`注解进行注册,使用方式和Spring MVC一致,在调用`Nut#run`方法前通过`Nut#getWebConfigure#addBasePackage`方法来添加要扫描的包路径。
> 因未使用ASM方式来解析参数名,如果在编译阶段也未将方法的参数名编译进去,在使用`@RequestParam`注解时需要手动指定参数名,如不指定参数名获取到的参数为类似`arg0`,将无法正确注入参数。自jdk1.8开始就提供了保留参数编译的功能`-parameters`。
#### 支持的注解
##### Controller注册
1. `@Controller`;
2. `@RestController`;
##### ControllerAdvice
1. `@ControllerAdvice`;
2. `@RestControllerAdvice`;
3. `@ExceptionHandler`;
##### 路径注册
1. `@RequestMapping`;
2. `@GetMapping`;
3. `@PostMapping`;
4. `@PutMapping`;
5. `@DeleteMapping`;
##### 参数绑定
1. `@CookieValue`;
2. `@RequestAttribute`;
3. `@SessionAttribute`;
4. `@ServerContextAttribute`;
5. `@PathVariable`;
6. `@RequestBody`;
7. `@RequestParam`;
##### 响应式
1. `@ResponseBody`;
##### IOC
1. `@Configuration`;
2. `@Bean`;
3. `@Component`;
4. `@Service`;
5. `@Repository`;
6. `@Autowired`;
7. `@Qualifier`;
##### 环境变量
1. `@Value`;
2. `@ConfigurationProperties`
> 使用`@RestController`、`@RestControllerAdvice`、`@ResponseBody`注解时,需要调用`Nut#getWebConfigure#getPluginManager#registerPlugin`提前向Nut注册`ResponsivePlugin`插件。
## 插件
Nut 提供了`ServerEventPlugin`、`ControllerPlugin`、`InterceptorPlugin`、`ExceptionHandlePlugin`四种拦截器。
1. `ServerEventPlugin`用来处理服务启动与关闭时的事件监听;
2. `ControllerPlugin`用来处理查找controller时的扩展操作;
3. `InterceptorPlugin`用来在controller处理业务前后执行扩展操作;
4. `ExceptionHandlePlugin`用来controller处理业务异常时进行异常扩展处理。
### Nut 内置插件
1. `ActuatorPlugin`:用来获取服务运行信息及服务远程关闭支持;
2. `AuthPlugin`:用来控制服务权限的插件;
3. `LoginPlugin`:用来限制用户登录后才能访问资源的插件;
4. `I18nPlugin`:用来支持国际化的插件;
5. `MemoryPlugin`:用来支持基于内存存储数据的插件,支持数据持久化(需要保证服务正常关闭);
6. `JdbcPlugin`:用来支持JDBC访问数据库的插件;
7. `ResponsivePlugin`:用来支持响应式输出的插件;
## 模板引擎
Nut已支持以下模板引擎:
1. Beetl;
2. Freemarker;
3. Thymeleaf;
## 自动探测
在引入指定依赖后,nut启动时会自动探测指定的类型是否存在以自动配置插件或模板引擎。 目前已支持以下自动探测类:
1. `ResponsivePluginAutoDetector`:响应式输出插件自动探测类,将以如下优先级进行探测:
1. `JackSon`;
2. `FastJson2`;
3. `FastJson`;
2. `ViewResolverAutoDetector`:视图解析器自动探测类,将以如下优先级进行探测:
1. `Thymeleaf`;
2. `FreeMarker`;
3. `Beetl`;
> 如需要开启指定的自动探测类,可调用`Nut#getWebConfigure#addIgnoreAutoDetectClass`方法指定要忽略的自动探测类。
## 启动
开发完毕后,通过`Nut#getWebConfigure#addBasePackage`方法来添加要扫描的包路径,调用`Nut#run`方法启动web服务。