对于JavaEE的初学者来说,大家学完JAVASE之后,马上进入了WEB阶段的学习。大家在JAVASE阶段写代码时,从头到尾都是自己写的,到了WEB阶段,尤其是进入Servlet的学习阶段,有人会感到困惑,怎么我写的代码看不到main函数了,服务端的Servlet是个什么东东呢?tomcat服务器到底底层做了哪些事情呢?为了帮助大家更好的理解tomcat服务器,也为了帮助大家更好的步入WEB的学习,我们特意安排了本次课程,动手实现Tomcat。我们自己手动的方式搭建一个Tomcat,让大家能够顺利的从JAVASE切换到JAVAWEB阶段的学习。
我们要实现一个Tomcat需要一些技术铺垫,这些技术分别是http协议和JavaSe的Socket编程。我们按照以下的顺序进行我们本次课程的学习。
-
HTTP协议
-
JavaSe的Socket编程
-
动手搭建Tomcat服务器
1.HTTP协议
1.1.HTTP协议概述
1.1.1协议
约定规则
1.1.2.网络协议
数据在网络上传输规则 http,pop3,pop,imap,ftp,流媒体协议
1.1.3.HTTP协议
htyper text transform protocal
超文本传输协议:如何在互联网上传输超文本
HTML:超文本标记语言 htyper text markup language
1.1.4.HTTP协议格式
由于我们平时访问互联网上的网页都是请求之后才响应.所以HTTP协议
基于请求-响应模型. 协议分为请求部分,响应部分.
1.1.5.HTTP协议分为请求部分,响应部分.
*_请求部分格式
请求行 请求头 请求体
*_响应部分格式:
响应行 响应头 响应体
1.2.请求部分(请求行/请求头/请求体)
POST /xxx/xxxx/xxx.png http/1.1
K1:v1
K2:v2
K3:v3
K4:v4
Username=tom&password=1234
1.2.1.请求行(请求方式 本次请求路径 协议/版本)
POST /index.html http/1.1(换行)
请求方式(POST/GET,一共设计7个方法)
本次请求哪些内容
本次请求采用的协议以及协议版本
1.2.2.请求头
作用:1告诉服务器对客户端描述2对本次请求描述
格式
K1:v1(换行)
K2:v2(换行)
K3:v3(换行)
空行
1.2.3.观察HTTP协议请求头
火狐浏览器下访问百度图片.
利用火狐===>工具===>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"
Host: 本次请求的主机路径
User-Agent:告诉服务端本次请求客户端所在的平台以及本次请求采用的浏览器百度中搜索:网速测试
Accept:用于指定客户端接受哪些类型的信息.eg:Accept:image/gif,表明客户端希望接受
GIF图象格式的资源;Accept:text/html,表明客户端希望接受html文本.
Accept-Language:告诉服务端,浏览器可以识别的语言种类
Accept-Encoding:告诉服务端,浏览器可以哪些类型压缩格式数据 gzip,defalte
1.2.4.请求体(约定用户的表单数据向服务端传递格式)
<form action=”http://www.baidu.com/aaa” method=”GET”>
<input type=”text” name=”username”/>
<input type=”password” name=”password”/>
<input type=”submit” value=”提交”/>
</form>
上面的表单中,当我们录入数据之后,点击提交按钮,数据以下述方式进行提交
GET /aaa?username=tom&pasword=123 http/1.1
请求头
请求头
请求头
请求头
空行
<form action=”www.baidu.com/aaa” method=”POST”>
<input type=”text” name=”username”/>
<input type=”password” name=”password”/>
<input type=”submit” value=”提交”/>
</form>
上面的表单中,当我们录入数据之后,点击提交按钮,数据以下述方式进行提交
POST /aaa http/1.1
请求头
请求头
请求头
请求头
username=tom&pasword=123
[请求体作用]存放客户端向服务端传递的数据
username=tom&userage=18&password=1234
Get /index.html?username=tom&password=123 http/1.1
请求头
请求头
空行
POST /index.html http/1.1
请求头
请求头
usernam=tom&password=1234
1.3.响应部分(响应行/响应头/响应体)
[响应部分格式]
HTTP/1.1 200 OK(换行)
K1:v1(换行)
K2:v2(换行)
K3:v3(换行)
空行
<html>
XXXX.....
</html>
1.3.1.响应行
HTTP/1.1 200 OK(回车换行)
本次响应采用的协议
状态码:协议设计之初,用一些数字描述了本次响应
状态描述:用文字本次响应进行简短描述
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求
常见状态代码,状态描述说明:
200 OK //客户端请求成功
404 Not Found //请求资源不存在,eg:输入了错误的URL"
1.3.2.响应头
作用:
1.服务端告诉浏览器服务端信息
2.对本次响应描述
格式:
K1:v1
K2:v2
K3:v3
空行
响应体
1.3.3.分析响应头
Date: Fri, 21 Apr 2017 02:04:54 GMT
Content-Type: text/html
Connection: keep-alive
Content-Encoding:gzip
Content-length
Server
Date:响应时间
content-Type:本次响应内容类型
content-Encoding:本次内容采用的压缩格式
content-length:本次内容长度
server:服务端采用的服务器类型
1.3.4.响应体
服务端响应到客户端部分,可能是HTML页面,JS文件,CSS文件,图片
2.Socket编程
关于Socket的基础知识我们不在赘述,Socket属于JAVASE的基础API。我们主要结合2段程序来验证HTTP协议传输规则。
2.1.案例一
2.1.1.案例需求
模拟浏览器向服务端起HTTP请求,接受来自服务端响应回客户端的数据
2.1.2.图形分析
2.1.3.代码实现
public class TestClient {
public static void main(String[] args) throws Exception {
Socket s=null;
OutputStream ops=null;
InputStream is=null;
try {
s=new Socket("www.itcast.cn",80);
ops=s.getOutputStream();
ops.write("GET /subject/about/index.html HTTP/1.1\n".getBytes());
ops.write("HOST:www.itcast.cn\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;
}
}
}
}
2.1.4.运行结果
如果我们通过浏览器访问
http://www.itcast.cn/subject/about/index.html
之后再页面中右击...>查看页面源代码,HTML页面源码中的内容和我们响应到客户端的响应体部分是一致的。
2.2.案例二
2.2.1.案例需求
从浏览器的地址栏输入localhost:8080 ,向本机的8080端口发起请求,服务端向客户端响应回去HTTP协议的响应部分
2.2.2.图形分析
2.2.3.代码实现
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("<html>");
buffer.append("<head>");
buffer.append("<title>");
buffer.append("我是标题");
buffer.append("</title>");
buffer.append("</head>");
buffer.append("<body>");
buffer.append("<h1>I am header </h1>");
buffer.append("<a href='http://www.baidu.com'>www.baidu.com</a>");
buffer.append("</body>");
buffer.append("</html>");
ops.write(buffer.toString().getBytes());
ops.flush();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(null!=ops) {
ops.close();
ops=null;
}
if(null!=socket) {
socket.close();
socket=null;
}
}
}
}
2.2.4.运行结果
3.实现Tomcat版本一
3.1.案例需求
-
在WebContent下发布静态资源demo.html , demo02.html
-
启动Tomcat服务器
-
当客户端对服务端发起不同的请求,localhost:8080/demo.html
-
服务端可以将对应的html页面响应到客户端
3.2.图形分析
"
3.3.代码实现
3.3.1.实现服务端的准备工作
-
定义静态变量WEB.ROOT,用于存放WebContent目录的绝对路径
-
定义静态变量url,存放本次请求服务端的静态资源的名称
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) {}
}
3.3.2.实现启动服务端代码
-
创建ServerSocket对象,监听本机8080端口
-
等待来自客户端的请求
public static void main(String[] args) {
//System.out.println(WEB.ROOT);
try {
// 建立不本机器的ServerSocket,监听本机器的80端口
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket socket = serverSocket.accept();
}
} catch (Exception e) {
e.printStackTrace();
}
}
3.3.3.实现服务端解析HTTP请求部分请求部分
-
读取HTTP协议请求部分数据
-
解析请求行,获取本次请求的静态资源名称
public static void main(String[] args) {
//System.out.println(WEB.ROOT);
try {
// 建立不本机器的ServerSocket,监听本机器的80端口
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket socket = serverSocket.accept();
//服务端的输入流
InputStream input = socket.getInputStream();
//服务端的输出流
OutputStream output = socket.getOutputStream();
//获取HTTP协议的请求部分,截取客户端要访问的资源名称,
//将这个资源名称赋值给url
parse(input);
}
} 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);
}
// 截取客户端请求资源的路径+名称 /demo.html
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;
}
3.3.4.实现服务端向客户端发送HTTP协议响应部分
-
通过输入流读取静态资源到服务端内存
-
将HTTP协议响应部分到客户端
public static void sendStaticResource(OutputStream output)
throws IOException {
byte[] bytes = new byte[2048];
FileInputStream fis = null;
try {
//cc.jpeg
File file = new File(TestServer2.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"
+ "<h1>File Not Found</h1>";
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();
}
}
4.实现Tomcat版本二
4.1.案例需求
当客户端发送请求到服务端的时候,可以运行服务端的一段JAVA代码,而且可以向客户端响应数据
4.2.图形分析
4.3.代码实现
4.3.1.定义一个接口Servlet
该接口是所有在服务端运行的JAVA小程序都要遵循的接口.
public interface Servlet {
public void init();
public void service(InputStream is,OutputStream ops)throws IOException;
public void destroy();
}
4.3.2.实现服务端的JAVA小程序 AAServlet,BBServlet
AAServlet.java
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.....");
}
}
BBServlet.java
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.....");
}
}
4.3.3.为服务端的2端JAVA小程序配置参数
conf.properties
aa=com.itcast.tomcateV2.AAServlet
bb=com.itcast.tomcateV2.BBServlet
4.3.4.服务器启动就读取配置参数
在TestServler中定义一个静态类型的MAP,用于存放配置文件中的参数信息
public static Map<String,String> map=new HashMap<String,String>();
public static void readConf() throws Exception{
Properties p=new Properties();
p.load(new FileInputStream(TestServer.WEB.ROOT+"\\conf.properties"));
Set keySet = p.keySet();
Iterator iterator = keySet.iterator();
while(iterator.hasNext()) {
String key=(String)iterator.next();
String value = p.getProperty(key);
map.put(key, value);
}
}
4.3.5.在版本一的基础上实现静态动态资源的响应
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小程序进行执行。最末,希望大家通过本案例可以对Tomcat有一个粗略的认识,可以顺利的过度到Servlet的学习以及JavaWeb阶段的学习,祝愿大家天天开心!
动手实现Tomcat
完整视频:http://yun.itheima.com/course/445.html?2010stt
配套资料:https://pan.baidu.com/s/1nmH8U4g_rNPWkX11WBhOfA 提取码:7i9o