此版本完成响应客户端的工作 这里先将ClientHandler中处理一次交互的第三步:响应客户端 实现出来。 目标:将一个固定的html页面通过发送一个标准的HTTP响应回复给浏览器使其呈现出来。
需要的知识点: 1:HTML基础语法,html是超文本标记语言,用于构成一个"网页"的语言。 2:HTTP的响应格式。
实现:
一:先创建第一个页面index.html
1:在src/main/resource下新建目录static 这个目录用于存放当前服务端下所有的网络应用中的静态资源。注:每个网路应用相当于一个"网站"的所有内容。而每个网络应用以一个独立的目录保存在static下,该目录的名字就是这个网络应用的名字。
2:在static目录下新建目录:myweb,作为我们的第一个"网站"
3:在myweb目录下新建第一个页面:index.html
二:实现将index.html页面响应给浏览器 在ClientHandler第三步发送响应处,按照HTTP协议规定的响应格式,将该页面包含在正文部分将其 发送给浏览器即可。
package com.webserver.core; import com.webserver.http.HttpServletRequest; import java.io.*; import java.net.Socket; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 该线程任务负责与指定客户端完成HTTP交互 * 与客户端交流的流程分成三步: * 1:解析请求 * 2:处理请求 * 3:发送响应 */ public class ClientHandler implements Runnable { private Socket socket; public ClientHandler(Socket socket) { this.socket = socket; } @Override public void run() { try { //1 解析请求 HttpServletRequest request = new HttpServletRequest(socket); //2 处理请求 /*通过请求对象,获取浏览器地址栏中的抽象路径部分*/ String path = request.getUri();//path:/myweb/index.html //3 发送响应 //定位要发送的文件(将src/main/resources/static/myweb/index.html) /*定义为resource目录(maven项目中src/main/java和src/main/resources实际上是一个目录) 只不过java目录中存放的都是.java的源代码文件 而resources目录下存放的就是非.java的其他程序中需要用到的资源文件 实际开发中,我们在定位目录时,常使用相对路径,而实际应用的相对路径都是类加载路径 类加载路径可以用: 类名.class.getClassLoader().getResource(".")就是类加载路径 这里可以理解为时src/main/java目录或src/main/resources 真是表达的是编译后这两个目录最终合并的target/classes目录。*/ File root = new File( ClientHandler.class.getClassLoader().getResource(".").toURI() ); /* root表达的是target/classes 从根开始寻找static目录 */ File staticDir = new File(root, "static"); /*在static目录下定位index.html文件*/ File file = new File(staticDir, path); System.out.println("资源是否存在:" + file.exists()); //3.1发送状态行 println("HTTP/1.1 200 OK"); //3.2发送响应头 println("Connection-Type:text/html"); println("Content-Length:" + file.length()); println(""); //3.3发送响应正文(index.html页面的数据) OutputStream out = socket.getOutputStream(); FileInputStream fis = new FileInputStream(file); int len; byte[] data = new byte[1024 * 10]; while ((len = fis.read(data)) != -1) { out.write(data, 0, len); } } catch (IOException | URISyntaxException e) { e.printStackTrace(); } finally { //HTTP协议要求,响应客户端后与TCP断开连接 try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } private void println(String line) throws IOException { OutputStream out = socket.getOutputStream(); byte[] data = line.getBytes(StandardCharsets.ISO_8859_1); out.write(data); out.write(13);//单独发送回车符 out.write(10);//单独发送换行符 } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>我的首页</title> </head> <body> <!-- <h1>--<h6>:标题,标题独立占一行,分别表示1级-6级标题 <center>:居中显示,该标签在html5中已经不再建议使用了。 <input>:输入组件,该组件用来在页面上获取用户的输入。 该组件有一个重要的属性:type,用来指定组件的样子。 常见的值有: type="text":文本框 type="password":密码框 type="radio":单选框 type="checkbox":复选框 type="button":按钮 <br>:换行 <a>:超链接,标签中间的文本就是超链接上显示的内容,href属性用于指定点击后 跳转的路径。 <table>标签:表格。属性border用于指定边框。 <table>标签中包含<tr>标签用于表示行 <tr>标签中包含<td>标签用于表示列 <td>标签中常见属性: align:对其方式。left左对齐,right右对齐,center剧中对其 colspan:跨列合并列,合并是从左向右合并列 rowspan:跨行合并列,合并是从上向下合并列 --> <center class="table"> <h1>百度</h1> <input type="text" size="32"> <input type="button" value="百度一下"><br> <!-- 单选框和复选框可以用name属性将其划分为一组,名字相同的为一组。 --> <input type="radio" name="gender">男 <input type="radio" name="gender">女<br> <a href="http://www.taobao.com">淘宝</a> <a href="http://www.jd.com">京东</a><br> <table border="1"> <tr> <td>第一列</td> <td>第二列</td> <td colspan="2" align="center">第三列</td> </tr> <tr> <td>第一列</td> <td rowspan="2">第二列</td> <td>第三列</td> <td>第四列</td> </tr> <tr> <td>第一列</td> <td>第三列</td> <td>第四列</td> </tr> </table> </center> <style> body{ color: #eee; background:url(https://img.3dmgame.com/uploads/images/thumbpicfirst/20190219/1550546216_738489.jpg) no-repeat center; background-size: cover; } </style> </body> </html>
完成404的响应 上一个版本中我们已经实现了根据浏览器中用户在地址栏上输入的URL中的抽象路径去 static目录下寻找对应资源进行响应的工作。
但是会存在路径输入有误,导致定位不对(要么定位的是一个目录,要么该文件不存在) 此时再发送响应的响应正文时使用文件输入流读取就会出现异常提示该资源不存在。
这是一个典型的404情况,因此我们在ClientHandler处理请求的环节,在实例化File 对象根据抽象路径定位webapps下的资源后,要添加一个分支,若该资源存在则将其响应 回去,如果不存在则要响应404状态代码和404页面提示用户。
实现: 1:在static下新建一个子目录root 该目录用于保存当前服务端所有网络应用共用的资源,比如404页面,因为无论请求哪个 网络应用中的资源都可能发生不存在的情况。 2:在root目录下新建页面:404.html 该页面居中显示一行字即可:404,资源不存在! 3:在ClientHandler处理请求的环节,当实例化File对象后添加一个分支,如果该File 对象存在且表示的是一个文件则将其响应给浏览器 否则发送的响应做如下变化: 1:状态行中的状态代码改为404,状态描述改为NotFound 2:响应头Content-Length发送的是404页面的长度 3:响应正文为404页面内容 完成后,在浏览器地址栏输入一个不存在的资源地址,检查服务端是否正确响应404页面
package com.webserver.core; import com.webserver.http.HttpServletRequest; import java.io.*; import java.net.Socket; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 该线程任务负责与指定客户端完成HTTP交互 * 与客户端交流的流程分成三步: * 1:解析请求 * 2:处理请求 * 3:发送响应 */ public class ClientHandler implements Runnable { private Socket socket; public ClientHandler(Socket socket) { this.socket = socket; } @Override public void run() { try { //1 解析请求 HttpServletRequest request = new HttpServletRequest(socket); //2 处理请求 /*通过请求对象,获取浏览器地址栏中的抽象路径部分*/ String path = request.getUri();//path:/myweb/index.html File root = new File(ClientHandler.class.getClassLoader().getResource(".").toURI()); File staticDir = new File(root, "static"); File file = new File(staticDir, path); System.out.println("资源是否存在:" + file.exists()); if (file.exists()){ //3.1发送状态行 println("HTTP/1.1 200 OK"); //3.2发送响应头 println("Connection-Type:text/html"); println("Content-Length:" + file.length()); println(""); }else { file = new File(staticDir,"/root/404.html"); println("HTTP/1.1 404 NotFound"); //3.2发送响应头 println("Connection-Type:text/html"); println("Content-Length:" + file.length()); println(""); } //3 发送响应 //3.3发送响应正文(index.html页面的数据) OutputStream out = socket.getOutputStream(); FileInputStream fis = new FileInputStream(file); int len; byte[] data = new byte[1024 * 10]; while ((len = fis.read(data)) != -1) { out.write(data, 0, len); } } catch (IOException | URISyntaxException e) { e.printStackTrace(); } finally { //HTTP协议要求,响应客户端后与TCP断开连接 try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } private void println(String line) throws IOException { OutputStream out = socket.getOutputStream(); byte[] data = line.getBytes(StandardCharsets.ISO_8859_1); out.write(data); out.write(13);//单独发送回车符 out.write(10);//单独发送换行符 } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>404</title> </head> <body> <center>404 NOT FOUNT 资源不存在</center> <style> body{ color: red; background:url(https://img.3dmgame.com/uploads/images/thumbpicfirst/20190219/1550546216_738489.jpg) no-repeat center ; background-size: cover; } </style> </body> </html>