# FHTTP **Repository Path**: kaxia-xia/fhttp ## Basic Information - **Project Name**: FHTTP - **Description**: 一个简洁的java http框架 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-06-28 - **Last Updated**: 2022-08-10 ## Categories & Tags **Categories**: web-dev-toolkits **Tags**: None ## README # FHTTP: 一个简洁易用的java http框架 **FHTTP抛弃了已有的web服务器,从serversocket开始, 直接构建http服务,从零编写http服务器,并对使用者提供简单的使用方法** **框架内部所有数据全部使用utf-8编码处理且不支持修改** [项目地址](https://gitee.com/forestrabbit/fhttp) version: 1.3.2 and 1.3.3 1.3.2 and 1.3.3修复提交文件名中文乱码问题以及其它一些bug version: 1.3.1 1.3.1修正了上传文件时的bug version: 1.3 ### 1.3新功能 1.3版本添加了对jwt token的支持,使用方法如下 设置一个验证器 ``` response.getJwtBuilder().withClaim("userName", "root").withExpiresAt( Date.from(LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault()).toInstant() )); response.setJwtPassord("123456789"); ``` 框架会将生成的token写入http header中的Authorization项目中 读取一个验证器 ``` @Token("123456789") public String handlerMain(Map claimMap) { if (claimMap.containsKey(key) && claimMap.get(key).asString().equals(value)) { return "

hello, world

"; } else { return "

please login first

"; } } ``` 如果一个mapping方法设置了Token注解,框架会在请求的request头中查找Authorization头,如果 没找到,抛出异常;找到的话,以Token注解的字符串为密码,验证token是否正确,如果正确,在参数中存在Map类型的情况下将claimMap传给函数,否则返回403 验证失败 1.3版本增加了对http长连接的支持,节省了创建socket的时间 1.3版本增加了@Raw注解,在方法上使用此注解,并在方法参数上使用OutputStream类型参数,可以在方法中 操作socket的输出流,直接写数据,例如 ``` @Raw @Mapping("/image_raw") public void handleRaw(OutputStream output) throws IOException { output.write("HTTP/1.1 200 OK\r\nContent-Type: image/jpeg\r\nTransfer-Encoding: chunked\r\n\r\n".getBytes(StandardCharsets.UTF_8)); var buffer = new byte[100]; try (var input = Test2.class.getClassLoader().getResourceAsStream("a.jpg")) { int n; while (true) { assert input != null; if ((n = input.read(buffer)) == - 1) break; output.write((Integer.toHexString(n) + "\r\n").getBytes(StandardCharsets.UTF_8)); output.write(buffer, 0, n); output.write("\r\n".getBytes(StandardCharsets.UTF_8)); } } output.write("0\r\n\r\n".getBytes(StandardCharsets.UTF_8)); } ``` 使用Raw注解后,框架仍然会将客户端传来的参数进行解析并传递到方法上, 但是,任何与输出流有关的信息都需要手动通过socket输出流设置 1.3版本支持如下方式打jar包 ```xml org.apache.maven.plugins maven-shade-plugin 3.2.1 package shade Main ``` version: 1.2.2 修复上传json的bug,支持查看未经解析的原始上传数据 ### 1.2.2新功能 1.2.2 版本支持查看未经解析的原始上传数据 ``` byte[] data = request.getRawData(); ``` version: 1.2.1 支持post表单上传json格式数据,支持查看上传表单文件的文件名 ### 1.2.1新功能 1.2.1 版本查看上传表单文件的文件名 ``` request.getFileInputStream(key).getFileName(); ``` version: 1.2 版本使用新的方法启动框架并且支持jar运行 ### 1.2新功能 1.2 版本使用如下方式启动框架 ``` new FHTTP(启动类.class).start(); ``` 框架会以启动类所在目录为根目录,自动寻找其它类 框架推荐使用如下maven插件打包 ```xml org.apache.maven.plugins maven-jar-plugin 3.2.0 Main true dependencies/ org.apache.maven.plugins maven-dependency-plugin 3.1.1 package copy-dependencies ${project.build.directory}/dependencies/ runtime true ``` version: 1.1.2 版本使用更新的方法获取用户上传的文件 示例:获取表单中name为img的文件 ``` try (var output = new FileOutputStream("...")) { try (var input = request.getFileInputStream("img")) { output.write(input.readAllBytes()); //获取文件的mime类型 String mime = input.getMimeType(); } } ``` version: 1.1.1 修复默认情况下框架加载内部404, 500页面失败问题 version: 1.1.0 增加404以及500的处理功能 ## 404和500 如果没有指定自定义的404以及500处理函数,框架会调用内部自带的一个简陋的404以及500页面, 如果想要进行自定义,可以在Controller中定义一个带有@Status("404 ...")或@Status("500 ...") 的方法,此方法不需要带有@Mapping注解,另外,被标注为404或500的处理函数,必须返回一个html页面。 框架建议,只定义一个404和500处理函数 version: 1.0.1 修复http方法为head时的bug version: 1.0 ## 使用方法 ### 启动服务器 首先,在main方法中,需要用以下代码开启一个http服务 ``` new FHTTP(启动类.class).start(); //详见1.2版本更新 //new FHTTP("项目类路径").start(); 过时 ``` 其中,项目类路径非常重要,框架将在这个路径上搜索其它代码, 一般选择main函数所在类的路径作为项目类路径(已过时) 以上代码会默认开启一个监听端口为8080的socket,如果想要更改端口, 请使用FHTTP类的setPort(int port)方法,例如 ``` //var fhttp = new FHTTP("项目类路径"); 过时 var fhttp = new FHTTP(启动类.class); //详见1.2版本更新 fhttp.setPort(8000); ``` 使用FHTTP的getPort方法可以获得当前监听的端口 ### 编写Controller 一旦框架启动,框架会在类路径范围内所有目录下搜索带有@Controller注解的类, 并寻找类中标有@Mapping注解的方法,一旦寻找成功,框架会将http请求中的url对应到相应的方法上,使用方法如下 ```java @Controller public class Test2 { @Mapping("/") public Object handleIndex() { return something; } @Mapping("/x") public void handleX() { //TODO } } ``` 当浏览器访问url为/时,框架将会调用Test2中的handleIndex方法, 当浏览器访问url为/x时,框架将会调用Test2中的handleX方法 Controller注解中也可以写入url,例如 ```java @Controller("/c1") public class Test2 { @Mapping("/") public Object handleIndex() { return something; } @Mapping("/x") public void handleX() { //TODO } } ``` 此时,如果想调用handleIndex方法,就需要浏览器访问url为/c1/, 想调用handleX方法,就需要浏览器访问url为/c1/x。 这样,不同的Controller类可以对应一组不同的url。 如何在方法中区分不同的http method,只需要在方法头部加入不同的注解即可, 框架支持get, post, put, delete, head 5种http方法。 如果在方法头部加上@Post注解,则该方法会对应到post方法上,如果此时用其它方法比如get访问对应的url,则会收到404错误。 相应的,@Put @Delete @Head注解会对应到相应的http方法,如果这些注解都没有,则默认对应到get方法。 ### Mapping方法 标记有@Mapping的方法,可以返回任意类型数据,也可以为void,框架内部有对应的处理方法。 框架规定,如果方法返回的数据可被解析为文本,则调用返回对象的toString方法,否则,将直接把返回对象转换为byte[]类型。 框架内部定义了一些注解,例如@JSON, @MIME, @Status,下面讲解这三个注解的使用方法。 #### @JSON 可以在方法头部加上@JSON注解,表示该方法返回的对象可以被json序列化,框架内部使用google的gson来序列化对象, 并向浏览器返回application/json的数据。 #### @MIME 在方法头部加上@MIME注解,表示该方法返回数据的mime类型,例如 ``` @MIME("text/javascript") public Object handle() {return "";} ``` 不要忘记@Mapping注解! #### @Status 在方法头部加上@MIME注解,表示该方法对应url的处理结果,例如 ``` @Status("200 OK") public Object handle() {return "";} ``` 不要忘记@Mapping注解! 框架内部会默认将处理结果设置为200 OK,所以若处理结果是200 OK将额外不必设置。 #### 方法参数 实际上框架支持任意多个参数的方法,这些参数有如下对应规则 1. 若参数的类型为Response, 框架会将当前浏览器请求对应的Response对象传给方法 2. 若参数的类型为Request, 框架会将当前浏览器请求对应的Request对象传给方法 3. 若参数标记有Param系列注解,框架会将对应的浏览器参数通过gson反序列化传给方法 以上参数没有先后顺序 Response方法 ``` public Object handle(Response response) {return "";} ``` Request方法 ``` public Object handle(Request request) {return "";} ``` 二者组合 ``` public Object handle(Request request, Response response) {return "";} ``` 不要忘记@Mapping注解! Param系列注解有三个,分别是@PathParam, @UrlParam, @BodyParam, 分别对应动态路由参数,get方法参数,post等参数在请求体的情况,使用方法如下 ```java /** * 定义一个javaBean */ public class User { private String userName; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } } ``` ```java @Controller public class Test2 { @Mapping("/name/:userName") public Object handleIndex(@PathParam User user) { //当url为/name/张三时,user.userName为张三 } @Mapping("/name") public void handleX(@UrlParam User user) { //当url为/name?userName=张三 时,user.userName为张三 } @Post @Mapping("/name") public void handlePost(@BodyParam User user) { //当通过表单或者javascript formdata body以post访问时,user.userName为张三 } } ``` #### Response类 参数中的Response类,提供了以下功能 ```java @Controller public class Test2 { @Mapping("/t1") public Object t1(Response response) { response.staticFile("文件路径"); //此方法向浏览器返回本地文件,并自动设置mime类型 response.getMimeType(); //获取当前数据的mimeType response.setMimeType(mimeType); //设置mimeType response.setHeadPair(key, value); //以键值对的形式设置response header,允许重复 response.setText(text); //设置response主体部分 response.setStatusCode(code); //设置url处理结果 response.setDispose(dispose); //设置response dispose response.setStaticCache(isCache); //设置是否开启staticFile内部缓存,默认开启 response.setCacheSecond(cacheSecond); //设置http强缓存有效时间,单位为秒,默认为两天 } } ``` staticFile读取文件时,默认会将文件缓存到内存中以加快读写速度,可以使用setStaticCache关闭 为加快速度,框架内部会对所有非文本类型数据作http缓存处理, 可以通过setCacheSecond设置强缓存有效时间。 mimeType设置的顺序是 方法内部response > MIME注解设置 > 默认设置 statusCode设置顺序是 方法内部response > Status注解设置 > 默认设置 #### Request类 参数中的Request类,提供了以下功能 ```java @Controller public class Test2 { @Mapping("/t1") public Object t1(Request request) { String value = request.getHeadValue(key); //获取request头部键数据 byte[] data = request.getParamValue(key); //获取一个request body体中的表单数据 //以下代码获取一个由body体传来的userName参数,过于麻烦,建议使用@BodyParam注解代替 var userName = new String(request.getParamValue("userName"), StandardCharsets.UTF_8).split(":::")[1]; //以下代码获取一个由body体传来的file二进制文件 byte[] buffer = new String(request.getParamValue("userName"), StandardCharsets.ISO_8859_1).split(":::")[1].getBytes(StandardCharsets.ISO_8859_1); } } ```