对于JavaEE的初学者来说,大家学完JAVASE之后,马上进入了WEB阶段的学习。大家在JAVASE阶段写代码时,从头到尾都是自己写的,到了WEB阶段,尤其是进入Servlet的学习阶段,有人会感到困惑,怎么我写的代码看不到main函数了,服务端的Servlet是个什么东东呢?tomcat服务器到底底层做了哪些事情呢?为了帮助大家更好的理解tomcat服务器,也为了帮助大家更好的步入WEB的学习,我们特意安排了本次课程,动手实现Tomcat。我们自己手动的方式搭建一个Tomcat,让大家能够顺利的从JAVASE切换到JAVAWEB阶段的学习。
我们要实现一个Tomcat需要一些技术铺垫,这些技术分别是http协议和JavaSe的Socket编程。我们按照以下的顺序进行我们本次课程的学习。
1)HTTP协议
2)JavaSE的Socket编程
3)动手搭建Tomcat服务器
2.1.1.什么是协议
协议就是一组约定规则。
2.1.2.网络协议
网络协议就是数据在网络上的传输规则,例如http,pop3,pop,imap,ftp,流媒体协议等。
2.1.3.HTTP协议
htyper text transform protocol
超文本传输协议:如何在互联网上传输超文本
HTML:超文本标记语言 htyper text markup language
2.1.4.HTTP协议格式
由于我们平时访问互联网上的网页都是请求之后才响应。所以HTTP协议基于请求-响应模型。协议分为请求部分,响应部分。
2.1.5.HTTP协议分为请求部分和响应部分
请求部分格式:请求行 请求头 请求体
响应部分格式:响应行 响应头 响应体
POST /XXX/XXX/XXX.png http/1.1 k1:v1 k2:v2 k3:v3 k4:v4 Username=tom&password=1234 |
2.2.1.请求行
格式:请求方式 本次请求路径 协议/版本
POST /index.html http/1.1(换行)**
1)请求方式(POST/GET/PUT/DELETE/HEAD/OPTION/TRANCE, 一共设计7个方法)
2)本次请求哪些内容
3)本次请求采用的协议以及协议版本
2.2.2.请求头
作用:1)告诉服务器对客户端描述
2)对本次请求描述
格式:
K1:v1(换行)
K2:v2(换行)
K3:v3(换行)
空行
2.2.3.观察HTTP协议请求头
火狐浏览器下访问百度图片。
https://ss0.bdstatic.com/5aV1bjqh.Q23odCf/static/superman/img/logo/bd.logo1.31bdc765.png](https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png |
利用火狐===>工具===>web开发者===>网络 观察本次请求过程
Host:"ss0.bdstatic.com" User-Agent:"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0" Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" Accept-Language:"zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3" Accept-Encoding:"gzip, deflate, br" Connection:"keep-alive" |
1)Host: 本次请求的主机路径
2)User-Agent:告诉服务端本次请求客户端所在的平台以及本次请求采用的浏览器
3)Accept:用于指定客户端接受哪些类型的信息.eg:Accept:image/gif,表明客户端希望接受GIF图象格式的资源;Accept:text/html,表明客户端希望接受html文本.
4)Accept-Language:告诉服务端,浏览器可以识别的语言种类
5)Accept-Encoding:告诉服务端,浏览器可以哪些类型压缩格式数据 gzip,defalte
2.2.4.请求体(约定用户的表单数据向服务端传递格式)
上面的表单中,当我们录入数据之后,点击提交按钮,数据以下述方式进行提交。
GET /aaa?username=tom&pasword=123 http/1.1 请求头 请求头 请求头 请求头 空行 |
上面的表单中,当我们录入数据之后,点击提交按钮,数据以下述方式进行提交。
POST /aaa http/1.1 请求头 请求头 请求头 请求头 username=tom&pasword=123 |
请求体的作用:存放客户端向服务端传递的数据
**[响应部分格式]**
HTTP/1.1 200 OK(换行) K1:v1(换行) K2:v2(换行) K3:v3(换行) 空行 XXXX..... |
2.3.1.响应行
HTTP/1.1 200 OK(回车换行) |
1)本次响应采用的协议
2)状态码:协议设计之初,用一些数字描述了本次响应
3)状态描述:用文字本次响应进行简短描述
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
1)1xx: 指示信息—表示请求已接收,继续处理
2)2xx: 成功--表示请求已被成功接收,理解,接受
3)3xx: 重定向--要完成请求必须进行更进一步操作
4)4xx: 客户端错误—请求有语法错误或请求无法实现
5)5xx: 服务器错误—服务器未能实现合法的请求
常见状态代码,状态描述说明:
200 OK //客户端请求成功
404 Not Found //请求资源不存在,eg:输入了错误的URL"
2.3.2.响应头
作用:
1)服务端告诉浏览器服务端信息
2)对本次响应描述
格式:
K1:v1 K2:v2 K3:v3 空行 响应体 |
2.3.3.分析响应头
Date: Fri, 21 Apr 2017 02:04:54 GMT Content-Type: text/html Connection: keep-alive Content-Encoding:gzip Content-length Server |
1)Date:响应时间
2)content-Type:本次响应内容类型
3)content-Encoding:本次内容采用的压缩格式
4)content-length:本次内容长度
5)server:服务端采用的服务器类型
2.3.4.响应体
服务端响应到客户端部分,可能是HTML页面,JS文件,CSS文件,图片。
关于Socket的基础知识我们不在赘述,Socket属于JAVASE的基础API。我们主要结合2段程序来验证HTTP协议传输规则。
3.1.1.案例需求
模拟浏览器向服务器端发起HTTP请求,接受来自服务器端响应回客户端的数据。
3.1.2.图形分析
3.1.3.代码实现
package com.me.mytomcat.mytomcatv1; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class TestClient { public static void main(String[] args) throws IOException { Socket s = null; OutputStream ops = null; InputStream is = null;
try { s = new Socket("www.baidu.com", 80); ops = s.getOutputStream(); ops.write("GET /subject/about/index.html HTTP/1.1\n".getBytes()); ops.write("HOST:www.baidu.com\n".getBytes()); ops.write("\n".getBytes()); is = s.getInputStream(); int i = is.read(); while(i != -1) { System.out.print((char)i); i = is.read(); } } catch (Exception e) { e.printStackTrace(); } finally { if(null != is) { is.close(); is = null; } if(null!=ops) { ops.close(); ops=null; } if(null!=s) { s.close(); s=null; } } }
} |
3.1.4.运行结果
如果我们通过浏览器访问对应的网址,之后再页面中右击...>查看页面源代码,HTML页面源码中的内容和我们响应到客户端的响应体部分是一致的。(案例中使用的网址本身访问会是404,所以此代码运行后的结果也是404)
3.2.1.案例需求
从浏览器的地址栏输入localhost:8080 ,向本机的8080端口发起请求,服务端向客户端响应回去HTTP协议的响应部分
3.2.2.图形分析
3.2.3.代码实现
package com.me.mytomcat.mytomcatv2; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class TestServer { // localhost:8080 public static void main(String[] args) throws IOException { ServerSocket ss = null; Socket socket = null; OutputStream ops = null;
try { ss = new ServerSocket(8080); while(true) { socket = ss.accept(); ops = socket.getOutputStream(); ops.write("HTTP/1.1 200 ok\n".getBytes()); ops.write("Content-Type:text/html;charset-utf-8\n".getBytes()); ops.write("Server:Apache-Coyote/1.1\n".getBytes()); ops.write("\n\n".getBytes()); StringBuffer buffer = new StringBuffer(); buffer.append(""); buffer.append(""); buffer.append(" buffer.append("我是标题"); buffer.append(""); buffer.append(""); buffer.append(""); buffer.append(" buffer.append("www.baidu.com"); buffer.append(""); buffer.append(""); ops.write(buffer.toString().getBytes()); ops.flush(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(null != ops) { ops.close(); ops = null; } if(null != socket) { socket.close(); socket = null; } } }
} |
3.2.4.运行结果
1)在WebContent下发布静态资源demo.html, demo02.html
2)启动Tomcat服务器
3)当客户端对服务端发起不同的请求:localhost:8080/demo.html
4)服务端可以将对应的html页面响应到客户端
4.3.1.实现服务端的准备工作
1)定义静态变量WEB_ROOT用于存放WebContent目录的绝对路径
2)定义静态变量url,存放本次请求服务端的静态资源名称
package com.me.mytomcat.mytomcatv3; public class TestServer { // 获取到项目下的WebContent目录 public static final String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";
// 代码客户端要请求的资源的路径 public static String url = "";
public static void main(String[] args) {
} } |
4.3.2.实现启动服务端代码
1)创建SercerSocket对象,监听本机8080端口
2)等待来自客户端的请求
package com.me.mytomcat.mytomcatv3; import java.net.ServerSocket; import java.net.Socket; public class TestServer { // 获取到项目下的WebContent目录 public static final String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";
// 代码客户端要请求的资源的路径 public static String url = "";
public static void main(String[] args) { try { // 建立本机器的ServerSocket,监听本机器的8080端口 ServerSocket serverSocket = new ServerSocket(8080); while(true) { Socket socket = serverSocket.accept(); } } catch(Exception e) { e.printStackTrace(); } } } |
4.3.3.实现服务端解析HTTP请求部分
1)读取HTTP协议请求部分数据
2)解析请求行,获取本次请求的资源路径
package com.me.mytomcat.mytomcatv3; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class TestServer { // 获取到项目下的WebContent目录 public static final String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";
// 代码客户端要请求的资源的路径 public static String url = "";
public static void main(String[] args) { try { // 建立本机器的ServerSocket,监听本机器的8080端口 ServerSocket serverSocket = new ServerSocket(8080); while(true) { Socket socket = serverSocket.accept();
// 服务端的输入流 InputStream inputStream = socket.getInputStream(); // 服务端的输出流 OutputStream outputStream = socket.getOutputStream();
//获取HTTP协议的请求部分,截取客户端要访问的资源名称, //将这个资源名称赋值给url parse(inputStream); } } catch(Exception e) { e.printStackTrace(); } }
public static void parse(InputStream input) throws IOException { StringBuffer content = new StringBuffer(2048); int i;
byte[] buffer = new byte[2048]; // 读取客户端发送过来的数据,将数据读取到字节数组buffer中. //i代表读取数据量的大小 311字节 i = input.read(buffer); System.out.println(buffer);
for(int j = 0; j < i; j++) { content.append((char)buffer[j]); } // 打印读取到的内容 System.out.print(content.toString());
// 截取客户端要请求的资源路径,demo.html String uri = parseUri(content.toString());
// 赋值给本类中静态成员 url = uri; System.out.println("uri..............:"+uri); }
/** * 截取客户端请求资源的路径 + 名称 * @param requestString * @return */ public static String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(' '); if(index1 != -1) { index2 = requestString.indexOf(' ', index1 + 1); if (index2 > index1) return requestString.substring(index1+2, index2); } return null; } } |
4.3.4.实现服务端向客户端发送HTTP协议响应部分
1)通过输入流读取静态资源到服务器内存
2)将HTTP协议响应部分到客户端
package com.me.mytomcat.mytomcatv3; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class TestServer { // 获取到项目下的WebContent目录 public static final String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";
// 代码客户端要请求的资源的路径 public static String url = "";
public static void main(String[] args) { try { // 建立本机器的ServerSocket,监听本机器的8080端口 ServerSocket serverSocket = new ServerSocket(8080); while(true) { Socket socket = serverSocket.accept();
// 服务端的输入流 InputStream inputStream = socket.getInputStream(); // 服务端的输出流 OutputStream outputStream = socket.getOutputStream();
//获取HTTP协议的请求部分,截取客户端要访问的资源名称, //将这个资源名称赋值给url parse(inputStream);
// 响应数据 sendStaticResource(outputStream); } } catch(Exception e) { e.printStackTrace(); } }
public static void parse(InputStream input) throws IOException { StringBuffer content = new StringBuffer(2048); int i;
byte[] buffer = new byte[2048]; // 读取客户端发送过来的数据,将数据读取到字节数组buffer中. //i代表读取数据量的大小 311字节 i = input.read(buffer); System.out.println(buffer);
for(int j = 0; j < i; j++) { content.append((char)buffer[j]); } // 打印读取到的内容 System.out.print(content.toString());
// 截取客户端要请求的资源路径,demo.html String uri = parseUri(content.toString());
// 赋值给本类中静态成员 url = uri; System.out.println("uri..............:"+uri); }
/** * 截取客户端请求资源的路径 + 名称 * @param requestString * @return */ public static String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(' '); if(index1 != -1) { index2 = requestString.indexOf(' ', index1 + 1); if (index2 > index1) return requestString.substring(index1+2, index2); } return null; }
public static void sendStaticResource(OutputStream output) throws IOException { byte[] bytes = new byte[2048]; FileInputStream fis = null;
try { File file = new File(TestServer.WEB_ROOT, url); if(file.exists()) { output.write("HTTP/1.1 200 OK\n".getBytes()); output.write("Server: Apache-Coyote/1.1\n".getBytes()); output.write("Content-Type: text/html;charset=UTF-8\n" .getBytes()); output.write("\n".getBytes());
fis = new FileInputStream(file); int ch = fis.read(bytes); while(ch != -1) { output.write(bytes, 0, ch); ch = fis.read(bytes); } } else { // file not found String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + " output.write(errorMessage.getBytes()); } } catch(Exception e) { // thrown if cannot instantiate a File object e.printStackTrace(); System.out.println(e.toString()); } finally { if(fis != null) { fis.close(); } } } } |
当客户端发送请求到服务端的时候,可以运行服务端的一段JAVA代码,而且可以向客户端响应数据。
5.3.1.定义一个Servlet
该接口是所有在服务端运行的JAVA小程序都要遵循的接口。
package com.me.mytomcat.mytomcatv4; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public interface Servlet { public void init(); public void service(InputStream is,OutputStream ops)throws IOException; public void destroy();
} |
5.3.2.实现服务端的Java小程序
1)AAServlet.java
package com.me.mytomcat.mytomcatv4; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class AAServlet implements Servlet { @Override public void init() { System.out.println("aa...init....."); } @Override public void service(InputStream is, OutputStream ops) throws IOException { System.out.println("AA service..."); ops.write("I am from Server...AA".getBytes()); ops.flush(); } @Override public void destroy() { System.out.println("aa...destroy....."); } } |
2)BBServlet.java
package com.me.mytomcat.mytomcatv4; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class BBServlet implements Servlet { @Override public void init() { System.out.println("bb...init....."); } @Override public void service(InputStream is, OutputStream ops) throws IOException { System.out.println("BB service..."); ops.write("I am from Server...BB".getBytes()); ops.flush(); } @Override public void destroy() { System.out.println("bb...destroy....."); } } |
5.3.4.为服务端的2端JAVA小程序配置参数
conf.properties
aa=com.me.mytomcat.mytomcatv4.AAServlet bb=com.me.mytomcat.mytomcatv4.BBServlet |
5.3.5.服务器启动就读取配置参数
在TestServer中定义一个静态类型的MAP,用于存放配置文件中的参数信息:
public static Map ……… public static void readConf() throws Exception { Properties properties = new Properties(); properties.load(new FileInputStream(TestServer.WEB_ROOT+"\\conf.properties"));
Set keySet = properties.keySet(); Iterator iterator = keySet.iterator(); while(iterator.hasNext()) { String key=(String)iterator.next(); String value = properties.getProperty(key); map.put(key, value); } } |
5.3.6.在版本一的基础上实现静态动态资源的响应
public static void sendDynamicResource(InputStream is,OutputStream ops) throws Exception{ ops.write("HTTP/1.1 200 OK\n".getBytes()); ops.write("Server:Apache-Coyote/1.1\n".getBytes()); ops.write("Content-Type:text/html;charset=UTF-8\n".getBytes()); ops.write("\n".getBytes()); if(map.containsKey(url)) { String value = map.get(url); Class clazz = Class.forName(value); Servlet ss = (Servlet)clazz.newInstance(); ss.service(is, ops); } } |
通过手动实现一个Tomcat,我们可以发现运行在服务端的JAVA小程序AAServlet或者是BBServlet本质上还是一段JAVA小程序,只不过我们只需要按照约定实现Servlet这个接口,只需要做好对应的配置信息,那么我们就可以通过浏览器向服务端发送请求,让服务端的这个JAVA小程序也就是AAServlet或者BBServlet小程序进行执行。