实现一个简易版的Tomcat(十三)

tech2026-09-27  3

 

前言

本版本开始,完成业务的处理,这里先以注册用户为例开始实现。

需要了解的知识点: 1:浏览器如何将用户在页面上输入的信息提交给服务端了解页面的"表单"元素。

2:服务端如何通过浏览器发送过来的请求解析到用户提交的数据本版本先将上述两个工作完成,因为这个步骤时通用的,无论用户将来 完成什么业务,只要提交数据,解析工作是一样的。

这一步完成后,再在下个版本中实现具体的处理注册用户的业务步骤。


实现:

1:在webapps/myweb/下新建一个页面:reg.html 注册页面在这里我们学习表单的使用(页面不是重点,所以略显粗糙。。。),代码如下:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户注册</title> </head> <body> <center> <h1>用户注册</h1> <!-- form表单 form是用来将用户在页面上输入的信息提交给服务端 使用的,需要注意只有被form表单包含的<input>中 用户输入的信息才会被提交。 form上有两个属性: action:用于指定当前表单提交的位置 method:表单提交的方法,有:get和post get:是地址栏形式的提交(内容在地址栏中) post:是打包提交(内容被包含在请求的消息正文中)。 --> <form action="./regUser" method="GET"> <table border="2"> <tr> <td>用户名</td> <td><input name="username" type="text"></td> </tr> <tr> <td>密码</td> <td><input name="password" type="password"></td> </tr> <tr> <td>确认密码</td> <td><input name="verifypwd" type="password"></td> </tr> <tr> <td>昵称</td> <td><input name="nickname" type="text"></td> </tr> <tr> <td colspan="2" align="center"> <input type="submit" value="注册"> </td> </tr> </table> </form> </center> </body> </html>

2:重构HttpRequest类,完成解析用户提交表单数据的操作。

在Request类里添加三个成员变量以便进一步解析uri需要在Request类里面创建一个解析uri的方法 /** * 进一步解析uri */ private void parseUri() { /* * uri会有两种不同的情况: * 1:含有参数 * 如果含有参数,要按照"?"将uri拆分,并将请求部分赋值给requestURI这个属性, * 将参数部分赋值给queryString这个属性,然后再将参数部分进一步拆分,将每一个 * 参数保存到parameters这个Map中,其中key是参数名,value是参数值。 * * 2:不含有参数 * 不含有参数则直接将uri赋值给requestURI即可,其余两个属性不需要做任何操作。 */ if (!uri.contains("?")) { requestURI = uri; } else { //先按照"?"拆分uri String[] data = uri.split("\\?"); requestURI = data[0]; if (data.length>1) { queryString = data[1]; //进一步拆分为一个参数 data = queryString.split("&"); //username="xxx"; for (String pairs : data) { //再将每个参数按照“=”拆分 String[] arr = pairs.split("="); if (arr.length>1) { parameters.put(arr[0], arr[1]); } else { parameters.put(arr[0], null); } } } } }

 

根据参数名获取参数值 /** * 根据给定的参数名获取对应的参数值 * @param name * @return */ public String getParameter(String name) { return parameters.get(name); }

 

 

接下来开始实现用户注册的业务处理

刚才我们在注册页面上的表单中action="./regUser"因此我们服务端解析请求后会得到一个请求:/myweb/regUser,并将其保存在HttpRequest中储存请求部分的属性requestURI中。

由于请求中可能含有参数,因此我们在ClientHandller处理请求的环节中不能再直接使用uri作为请求路径使用了,应当改为使用属性requestURI, 因为它是用来保存uri中的请求部分。

因此修改ClientHandler处理请求的环节,首先获取requestURI的值作为请求,然后添加一个新的分支判断,看看它的值是不是注册页面表单提交上来的"/myweb/regUser",如果是则处理用户注册,否则再执行原来的分支,看看是不是webapps下的一个文件等后续处理操作.。

将之前ClientHandler里的代码修改为如下: 

这样一来,当用户在注册页面提交表单时,则会走到处理用户注册的分支,我们来完成这个工作,具体步骤如下:

1:新建一个包:com.webserver.servlet,在这个包中保存所有用于处理某个特定业务的类。

2:在servlet包新建类:RegServlet,用于处理用户注册业务。

3:注册业务代码如下:

/** * 处理用户注册 * @Author JIANG */ public class RegServlet { public void service(HttpRequest request, HttpResponse response) { System.out.println("RegServlet:开始处理注册..."); //1:通过request获取用户在注册页面上输入的注册信息 //注意这里的参数要和页面上对应输入框的名字一致(name属性的值) String username = request.getParameter("username"); String password = request.getParameter("password"); String ageStr = request.getParameter("age"); String nickname = request.getParameter("nickname"); /* * 这里做一个验证工作,首先,如果用户输入的上述四项内容有null或 * 年龄不是一个数字时跳转到一个错误的提示页面。 * 错误提示页:login_info_err.html * 里面居中一行字:注册信息有误,请重新注册。 * 然后可以添加一个超链接重新回到注册页面。 * * 提示:不是数字的判定要用正则表达式 */ if (username==null || password==null || nickname==null || ageStr==null || !ageStr.matches("\\d{1,2}") ){ response.setEntity(new File("webapps/myweb/reg_info_err.html")); return; } System.out.println(username + "," + password + "," + ageStr + "," + nickname); int age = Integer.parseInt(ageStr); //2:将注册信息写入文件user。dat中保存 /* * user.dat文件中保存所有注册用户的信息,每个用户占用100字节,其中 * 用户名,密码,昵称为字符串各占32字节,年龄为int值占用4字节。 * * 使用RandomAccessFile完成将当前用户写入 * user.dat文件中,该文件存放在项目目录下即可 */ try ( RandomAccessFile raf = new RandomAccessFile("user.dat", "rw"); ) { /* * 先读取user.dat文件中现有的所以记录,将每个用户的用户名与当前注册的用户名比对, * 如果重复则相应该用户已存在的提示页面。 * 只有文件中不存在该用户时才执行下面的注册操作。 */ File file = new File("user.dat"); for (int i = 0;i<file.length()/100;i++) { raf.seek(i*100); byte[] data = new byte[32]; raf.write(data); String name = new String(data,"UTF-8").trim(); if (name.equals(username)) { response.setEntity(new File("webapps/myweb/hava_user.html")); return; } } raf.seek(raf.length()); byte[] bytes = Arrays.copyOf(username.getBytes(), 32); raf.write(bytes); bytes = Arrays.copyOf(password.getBytes(), 32); raf.write(bytes); bytes = Arrays.copyOf(nickname.getBytes(), 32); raf.write(bytes); raf.writeInt(age); //3:通过设置response将注册结果页面响应给客户端 response.setEntity(new File("webapps/myweb/reg_success.html")); } catch (IOException e) { e.printStackTrace(); } System.out.println("RegServlet:处理注册完毕!"); } }

注:根据业务的需求,还需要在webapps/myweb目录下再创建一个用户名已存在页面(have_user.html) 和一个注册失败页面(reg_info_err.html),由于页面组成过于简单,这里就不再上传代码片段了。

注册功能基本实现完成,但是还有一些不足,接下来解决这些不足。

传递中文问题: 当我们在注册页面上输入中文,那么提交这个表单如果使用GET(关于get和post请求后面的版本会详细说明)形式的话,输入框的内容都会包含在地址栏的"?"右侧。而这部分信息最终体现在请求的请求行中的抽象路径部分里。HTTP协议要求请求的请求行和消息头出现的字符只能符合ISO8859,这个字符集不支持中文,因此我们不能直接传递中文给服务端。

解决办法: 浏览器会将中文先按照指定的字符集转换为一组字节,然后每个字节用两位16进制形式表示,并在前面用一个%标注,形成%xx这样的内容,以此来表示一字节信息,这个是URL地址格式规定的。因此服务端在接收到这些%xx的内容后可以在将xx部分的16进制还原为2进制, 再按照浏览器使用的字符集还原对应的文字即可。

实现:

只需要在Request类里面的parseUri()里面添加如下代码即可:

注:16进制出现的字符只有0123456789ABCDEF 这些字符ISO8859-1都支持,所以我们用这样的形式传递既可满足HTTP协议要求,又可以做到传递非ISO8859-1的字符内容。


至此,注册业务的功能已经实现完毕,下个版本将实现登录业务。

最新回复(0)