# 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服务。