手写Web服务器(三)
15.解决浏览器传递中文问题
由于HTTP协议只支持ISO8859-1, 此字符集不支持中文, 导致请求中包含中文时, 无法正确传递中文. 在UTF-8中每个中文用三个字节表示, 一个方案是在请求前,先将中文按照UTF-8转换为3个字节再传递, 比如若是想要传输姓名,则需要24个字节, 带来的问题,数据量大传递的速度慢,而且太冗长 .
另外一种方案, 使用十六进制简化二进制表达! 此时带来另外问题:如何与实际的英文数字组合区分呢?方案:如果想表示16进制内容而不是英文, 每两位16进制前使用%
最终方案:在HttpServletRequest类中的parseParameters()方法中先对请求路径中的中文部分使用API URLDecode(line, "UTF-8")转换为中文
拓展知识: 有Unicode编码为什么需要UTF-8?
例子:QQ聊天中,A传了一个中文给B, 由于使用Unicode是一个中文用两个字节表达,一个英文/符号用一个字节表达, 计算机底层在传输过程中底层传输的都是二进制字节, 那么B应该把这两个字节当成两个英文或符号还是中文,使用UTF-8则解决了这一问题
private void parseParameters(String line){//先将参数转换为中文try {line = URLDecoder.decode(line,"UTF-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}String[] data = line.split("&");//将参数部分按照"&"拆分出每一组参数for(String para : data){//para: username=zhangsanString[] paras = para.split("=");parameters.put(paras[0],paras.length>1?paras[1]:"");}}
16.实现处理动态页面
原方案:在前端控制器类中设置/userList映射处理, 在处理器中生成一个userList.html响应给浏览器 这种方案存在并发安全问题和性能问题
- 涉及到硬盘的读文件和写文件,有性能问题
- 多个浏览器同时对userList.html进行写操作,线程不安全
解决方案: 使用ByteArrayOutputStream存储动态数据,并且以此数据是否为null,进行sendContent()方法重构,新增 getOutputStream方法和getWriter()方法,setContentType()方法
此需求即请求是不是一个业务处理,若不是业务处理(即ByteArrayOutputStream为null
)而是请求资源是否存在对应资源,若有,则设置好响应的静态资源,若无则重定向至404
public class HttpServletResponse {private Socket socket;//状态行的相关信息private int statusCode = 200; //状态代码private String statusReason = "OK"; //状态描述//响应头相关信息private Map<String,String> headers = new HashMap<>(); //响应头//响应正文相关信息private File contentFile; //正文对应的实体文件private ByteArrayOutputStream baos;public HttpServletResponse(Socket socket){this.socket = socket;}/*** 将当前响应对象内容以标准的HTTP响应格式,发送给客户端(浏览器)*/public void response() throws IOException {//发送前的准备工作sendBefore();//3.1发送状态行sendStatusLine();//3.2发送响应头sendHeaders();//3.3发送响应正文sendContent();}//发送前的准备工作private void sendBefore(){//判断是否有动态数据存在if(baos!=null){addHeader("Content-Length",baos.size()+"");}}//发送状态行private void sendStatusLine() throws IOException {println("HTTP/1.1" + " " + statusCode + " " +statusReason);}//发送响应头private void sendHeaders() throws IOException {/*当发送响应头时,所有待发送的都应当都作为键值对存入了headers中headers:key valueContent-Type text/htmlContent-Length 245Server WebServer... ...*/Set<Map.Entry<String,String>> entrySet = headers.entrySet();for(Map.Entry<String,String> e : entrySet){String name = e.getKey();String value = e.getValue();println(name + ": " + value);}//单独发送一组回车+换行表示响应头部分发送完了!println("");}//发送响应正文private void sendContent() throws IOException {if(baos!=null){//存在动态数据byte[] data = baos.toByteArray();OutputStream out = socket.getOutputStream();out.write(data);}else if(contentFile!=null) {try (FileInputStream fis = new FileInputStream(contentFile);) {OutputStream out = socket.getOutputStream();int len;byte[] data = new byte[1024 * 10];while ((len = fis.read(data)) != -1) {out.write(data, 0, len);}}}}public void sendRedirect(String path){statusCode = 302;statusReason = "Moved Temporarily";addHeader("Location",path);}/*** 向浏览器发送一行字符串(自动补充CR+LF)* @param line*/private void println(String line) throws IOException {OutputStream out = socket.getOutputStream();out.write(line.getBytes(StandardCharsets.ISO_8859_1));out.write(13);//发送回车符out.write(10);//发送换行符}public int getStatusCode() {return statusCode;}public void setStatusCode(int statusCode) {this.statusCode = statusCode;}public String getStatusReason() {return statusReason;}public void setStatusReason(String statusReason) {this.statusReason = statusReason;}public File getContentFile() {return contentFile;}public void setContentFile(File contentFile) throws IOException {this.contentFile = contentFile;String contentType = Files.probeContentType(contentFile.toPath());//如果根据文件没有分析出Content-Type的值就不添加这个头了,HTTP协议规定服务端不发送这个头时由浏览器自行判断类型if(contentType!=null){addHeader("Content-Type",contentType);}addHeader("Content-Length",contentFile.length()+"");}public void addHeader(String name,String value){headers.put(name,value);}/*** 通过返回的字节输出流写出的字节最终会作为响应正文内容发送给客户端* @return*/public OutputStream getOutputStream(){if(baos==null){baos = new ByteArrayOutputStream();}return baos;}public PrintWriter getWriter(){OutputStream out = getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(out,StandardCharsets.UTF_8);BufferedWriter bw = new BufferedWriter(osw);PrintWriter pw = new PrintWriter(bw,true);return pw;}/*** 添加响应头Content-Type* @param mime*/public void setContentType(String mime){addHeader("Content-Type",mime);}
}
17.利用反射注解机制实现处理请求
当我们得到本次请求路径path的值后,我们首先要查看是否为请求业务:
- 扫描controller包下的所有类
- 查看哪些被注解@Controller标注的过的类(只有被该注解标注的类才认可为业务处理类)
- 遍历这些类,并获取他们的所有方法,并查看哪些时业务方法只有被注解@RequestMapping标注的方法才是业务方法
- 遍历业务方法时比对该方法上@RequestMapping中传递的参数值是否与本次请求路径path值一致?如果一致则说明本次请求就应当由该方法进行处理,因此利用反射机制调用该方法进行处理。
- 如果扫描了所有的Controller中所有的业务方法,均未找到与本次请求匹配的路径
则说明本次请求并非处理业务,那么执行下面请求静态资源的操作
public class DispatcherServlet {private static DispatcherServlet servlet;private static File rootDir;private static File staticDir;static {servlet = new DispatcherServlet();try {//定位到:target/classesrootDir = new File(DispatcherServlet.class.getClassLoader().getResource(".").toURI());//定位static目录staticDir = new File(rootDir, "static");} catch (URISyntaxException e) {e.printStackTrace();}}private DispatcherServlet() {}public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {//不能直接使用uri作为请求路径处理了,因为可能包含参数,而参数内容不是固定信息。String path = request.getRequestURI();//判断本次请求是否为请求某个业务try {File dir = new File(rootDir,"com/webserver/controller");File[] subs = dir.listFiles(f->f.getName().endsWith(".class"));for(File sub : subs){String fileName = sub.getName();String className = fileName.substring(0,fileName.indexOf("."));className = "com.webserver.controller."+className;Class cls = Class.forName(className);if(cls.isAnnotationPresent(Controller.class)){Object obj = cls.newInstance();//实例化这个ControllerMethod[] methods = cls.getDeclaredMethods();for(Method method : methods){if(method.isAnnotationPresent(RequestMapping.class)){RequestMapping rm = method.getAnnotation(RequestMapping.class);String value = rm.value();if(path.equals(value)){//如果请求路径与该方法中@RequestMapping注解的参数值一致method.invoke(obj,request,response);return;//业务处理完直接接触处理请求的操作(不走下面的处理静态页面操作)}}}}}} catch (Exception e) {e.printStackTrace();}File file = new File(staticDir, path);System.out.println("该页面是否存在:" + file.exists());if (file.isFile()) {//用户请求的资源在static目录下存在且是一个文件response.setContentFile(file);} else {response.setStatusCode(404);response.setStatusReason("NotFound");response.setContentFile(new File(staticDir, "/root/404.html"));}//测试添加其它响应头response.addHeader("Server", "WebServer");}public static DispatcherServlet getInstance() {return servlet;}}
17.重构DispatcherServlet
将判断本次请求是否为请求某个业务的代码提取到新建的类HandllerMapping中, 实现解耦合
public class HandlerMapping {private static Map<String, Method> mapping = new HashMap<>();static{init();}private static void init(){try {File dir = new File(HandlerMapping.class.getClassLoader().getResource("./com/webserver/controller").toURI());File[] subs = dir.listFiles(f->f.getName().endsWith(".class"));for(File sub : subs){String fileName = sub.getName();String className = fileName.substring(0,fileName.indexOf("."));className = "com.webserver.controller."+className;Class cls = Class.forName(className);if(cls.isAnnotationPresent(Controller.class)){
// Object obj = cls.newInstance();//实例化这个ControllerMethod[] methods = cls.getDeclaredMethods();for(Method method : methods){method.getDeclaringClass();if(method.isAnnotationPresent(RequestMapping.class)){RequestMapping rm = method.getAnnotation(RequestMapping.class);String value = rm.value();mapping.put(value,method);}}}}} catch (Exception e) {e.printStackTrace();}}/*** 根据请求路径获取处理该请求的某Controller的对应方法* @param path* @return*/public static Method getMethod(String path){return mapping.get(path);}
DispatchServlet
public class DispatcherServlet {private static DispatcherServlet servlet;private static File rootDir;private static File staticDir;static {servlet = new DispatcherServlet();try {//定位到:target/classesrootDir = new File(DispatcherServlet.class.getClassLoader().getResource(".").toURI());//定位static目录staticDir = new File(rootDir, "static");} catch (URISyntaxException e) {e.printStackTrace();}}private DispatcherServlet() {}public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {//不能直接使用uri作为请求路径处理了,因为可能包含参数,而参数内容不是固定信息。String path = request.getRequestURI();//根据请求路径判断是否为处理某个业务try {Method method = HandlerMapping.getMethod(path);//path:/userListif(method!=null){method.invoke(method.getDeclaringClass().newInstance(),request,response);return;}} catch (Exception e) {e.printStackTrace();}File file = new File(staticDir, path);System.out.println("该页面是否存在:" + file.exists());if (file.isFile()) {//用户请求的资源在static目录下存在且是一个文件response.setContentFile(file);} else {response.setStatusCode(404);response.setStatusReason("NotFound");response.setContentFile(new File(staticDir, "/root/404.html"));}//测试添加其它响应头response.addHeader("Server", "WebServer");}public static DispatcherServlet getInstance() {return servlet;}}
手写Web服务器(三)相关推荐
- 手写Web服务器(二)
6.实现响应404页面 上一个版本中我们已经实现了根据浏览器中用户在地址栏上输入的URL中的抽象路径去 static目录下寻找对应资源进行响应的工作. 但是会存在路径输入有误,导致定位不对(要么定位的 ...
- 手写webserver服务器
手写webserver服务器 文章目录 手写webserver服务器 前言 一.web server执行流程 组件说明 项目地址 二.代码实现 三. 效果展示 四.总结 前言 webserver 服务 ...
- Golang之手写web框架
Go手写Web框架 1.1 标准启动方式 通过定义接口,使用 net/http 库封装基础的功能,通过自定义函数的方式可以自定义 StandardStart.go // Handler 用于实现处理器 ...
- TinyWS —— 一个C++写的简易WEB服务器(三)
写在前面 代码已经托管在 https://git.oschina.net/augustus/TinyWS.git 可以用git clone下来.由于我可能会偶尔做一些修改,不能保证git 库上的代码与 ...
- 自己动手写web服务器一(浏览器的访问信息)
要协议一个web服务器,需要了解http协议,下面我们来看一下当浏览器请求网张的时候向web服务器发送的数据,我使用的是ubuntu 中telent展现一个下过程.我需要一个简单的网站来演示一下,我装 ...
- iOS网络构架 与 web服务器 (三次握手)
iOS中的网络构架 (从上层到下层依次为Cocoa\Web Kit\Core Foundation\OS\Hardware) Cocoa (面向对象的框架集) 包含基于OC的API Cocoa层是基 ...
- 三分钟手写RPC调用 (三)
三分钟springboot整合dubbo3(三)(手写RPC调用) 1.设计思路 2.代码编写 3.总结 1.设计思路 1.启动一个springboot项目,定义一个简单的HelloWord程序,能通 ...
- 从零开始手写VIO第三章作业(含关键点细节及思维过程)
文章目录 前言·与同主题博文的不同 1.代码修改 1.1阻尼因子 µ 随着迭代变化的曲线图 1.2完成曲线y = ax^2^ + bx + c的参数估计 1.3实现其他更优秀的阻尼因子策略 2.公式推 ...
- 服务器 搭建虚拟主机,server2016下搭建web服务器三种虚拟主机实验文档
一.实验任务 网站家目录--路径 :c/inetpub/wwwroot 默认文档格式 : 查看默认文档,最常用index.html 更改文档要打开后缀显示 将唐诗网站加到自己的网站中运行 二. ...
最新文章
- jquery 获取某个值得键名_jQuery获取元素索引值index()方法
- Android 一些比较厉害的大牛博客
- 分布式监控系统Zabbix3.2添加自动发现磁盘IO并注册监控
- 【Makefile由浅入深完全学习记录3】伪目标的引入
- Laravel框架一:原理机制篇
- 获取ajax传过来的数据_大数据编辑问题
- MySQL数据库之MyISAM与InnoDB的区别
- win10安装mysql zip_win10 安装mysql zip 压缩包版
- php dao 接口,SSH中公共Dao层、Dao接口
- MESOS集群高可用部署
- Structs1.0入门知识
- php公众号支付开发,微信公众号支付开发
- 洛必达法则-求导的方法求解出极限
- 纯前端word导出echarts图表
- 海洋cms播放器html,海洋cms怎么添加播放器
- mach_absolute_time 高效计算时间差
- rockchip的调试手段
- 2022紫光展锐数字芯片提前批笔试
- python统计单词个数算法_python 统计单词个数和频次
- Springboot 搭建oss文件上传服务器