Tomcat本质上就一个请求+响应请求的JAVA程序,当我们从浏览器输入一个url时,我们将发送请求到Tomcat上,tomcat对该请求进行解析,并将响应的内容返回浏览器。
Tomcat通过Socket+HTTP协议进行实现,这里做了一个简单的流程图。
下面简单介绍下HTTP协议:
HTTP协议
- HTTP协议运行于TCP协议之上,默认端口80,HTTP则是443。
- HTTP协议都是客户端发起请求,是一个无状态协议,这次请求和上次没有对应关系。
- HTTP请求(响应)报文包括:请求行(状态行)、首部、空行和实体。
请求头(重点几个)
- Host:本次请求主机的路径
- User-Agent:告诉服务器本次请求客户端所在的平台以及本次请求采用的浏览器
- Accept:用于指定客户端接收哪些类型的信息,*/*都接收
- Accept-Language:语言
- Accept-Encoding:压缩数据的格式,例如gzip,表示浏览器可以识别gzip类型的数据
响应头(重点几个)
- Date:响应事件
- content-Type:本次响应内容类型
- content-Encoding:本次内容采用的压缩格式
- content-length:本次内容长度
动手实现
当请求是静态请求,即请求一个html界面的时候,我们只需要读取html文件的内容,并组织成http响应报文即可。这里给出两个html:
而如果是动态的请求,则就是servlet,通过java代码返回响应体,这里我们配置两个简单servlet在配置文件中:
aa=com.fty.tomcatv2.AAServlet
bb=com.fty.tomcatv2.BBServlet
并实现简单java代码:
package com.fty.tomcatv2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public interface Servlet {//所有服务端JAVA要实现的接口
//初始化
public void init();
//服务
public void Service(InputStream is, OutputStream os) throws IOException;
//销毁
public void destroy();
}
这是servlet的整体接口,而我们的service就是具体的业务逻辑
package com.fty.tomcatv2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class AAServlet implements Servlet {
public void init() {
System.out.println("aaServlet...init");
}
public void Service(InputStream is, OutputStream os) throws IOException {
System.out.println("aaServlet...service");
os.write("I'm from AAServlet".getBytes());
os.flush();
}
public void destroy() {
System.out.println("aa...destroy...");
}
}
package com.fty.tomcatv2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class BBServlet implements Servlet {
public void init() {
System.out.println("bbServlet...init");
}
public void Service(InputStream is, OutputStream os) throws IOException {
System.out.println("bbServlet...service");
os.write("I'm from BBServlet".getBytes());
os.flush();
}
public void destroy() {
System.out.println("bb...destroy...");
}
}
具体的servlet的实现类,每个servlet可以看成不同业务,即对不同的请求进行特定的响应。最后给出最重要的代码,已经进行很详细的注释:
package com.fty.tomcatv2;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
public class TestServer {
//获取resources路径
public static String WEB_ROOT = TestServer.class.getClassLoader().
getResource("./").getPath();
//请求的内容,域名后的地址
private static String url = "";
//定义一个静态类型的MAP,存储服务conf.properties中的配置信息
private static Map<String, String> map = new HashMap<String, String>();
static {
//服务器启动之前将配置参数中的信息加载到MAP中
//创建一个Properties对象
Properties prop = new Properties();
try {
prop.load(new FileInputStream(WEB_ROOT + "conf.properties"));
//将配置文件中的数据读取到Map中
Set set = prop.keySet();
Iterator iter = set.iterator();
while (iter.hasNext()) {
String key = (String) iter.next();
String value = prop.getProperty(key);
map.put(key, value);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket socket = null;
OutputStream outputStream = null;
InputStream inputStream = null;
try {
//创建ServerSocket,监听本机80端口
serverSocket = new ServerSocket(8080);
while (true) {
//获取到客户端对应的socket
socket = serverSocket.accept();
//获取输入输出流对象
outputStream = socket.getOutputStream();
inputStream = socket.getInputStream();
//获取HTTP请求部分并解析请求
//判断本次请求是静态demo.html还是运行在服务端的一段JAVA小程序
parse(inputStream);
if(null != url) {
if(url.indexOf(".") != -1) {
//发送静态资源文件
sendStaticResource(outputStream);
} else {
//发送动态资源
sendDynamicResource(outputStream, inputStream);
}
}
relaxStream(outputStream, inputStream);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
relaxStream(outputStream, inputStream);
if(null != socket) {
socket.close();
socket = null;
}
}
}
private static void relaxStream(OutputStream outputStream, InputStream inputStream) throws IOException {
if(null != outputStream) {
outputStream.close();
outputStream = null;
}
if(null != inputStream) {
inputStream.close();
inputStream = null;
}
}
private static void sendDynamicResource(OutputStream outputStream, InputStream inputStream) throws Exception {
//将HTTP协议的响应行和响应头发送到客户端
outputStream.write("HTTP/1.1 200 OK\n".getBytes());
outputStream.write("Server:apache-Coyote/1.1\n".getBytes());
outputStream.write("Content-Type:text/html;charset=utf-8\n".getBytes());
outputStream.write("\n".getBytes());
//判断map中key是否和本次待请求的资源路径一致
if(map.containsKey(url)) {
//如果包含指定的key,获取到map中key对应的value部分
String value = map.get(url);
//通过反射将对应的java程序加载到内存
Class clazz = Class.forName(value);
//执行init方法
Servlet servlet = (Servlet) clazz.newInstance();
//执行service方法
servlet.init();
servlet.Service(inputStream, outputStream);
}
}
private static void sendStaticResource(OutputStream outputStream) throws IOException {
//发送静态资源
//定义一个文件输入流,用户获取静态资源demo01.html中的内容
byte[] bytes = new byte[2048];
FileInputStream fileInputStream = null;
try {
//创建文件对象File,代表本次要请求的资源demo01.html
File file = new File(WEB_ROOT, url);
//如果文件存在
if(file.exists()) {
//向客户端输出HTTP协议的响应行/头
outputStream.write("HTTP/1.1 200 OK\n".getBytes());
outputStream.write("Server:apache-Coyote/1.1\n".getBytes());
outputStream.write("Content-Type:text/html;charset=utf-8\n".getBytes());
outputStream.write("\n".getBytes());
//获取到文件输入流对象
fileInputStream = new FileInputStream(file);
//读取静态资源demo01.html中的内容到数组中
int ch = fileInputStream.read(bytes);
while (ch!=-1) {
//将读取到数组中的内容通过输出流发送到客户端
outputStream.write(bytes, 0, ch);
ch = fileInputStream.read(bytes);
}
} else {
//如果文件不存在
//向客户端响应文件不存在消息
outputStream.write("HTTP/1.1 404 not found\n".getBytes());
outputStream.write("Server:apache-Coyote/1.1\n".getBytes());
outputStream.write("Content-Type:text/html;charset=utf-8\n".getBytes());
outputStream.write("\n".getBytes());
String errorMessage = "file not found";
outputStream.write(errorMessage.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(null != null) {
fileInputStream.close();
fileInputStream = null;
}
}
}
private static void parse(InputStream inputStream) throws IOException {
//读取请求部分,截取请求资源名称
//定义一个变量,存放HTTP协议请求部分的数据
StringBuffer content = new StringBuffer(2048);
//定义一个数组,存放HTTP协议请求部分数据
byte[] buffer = new byte[2048];
//定义一个变量i,代表读取数据到数组中之后,数据量的大小
int i = -1;
//读取客户端发送过来的数据,将数据读取到字节组buffer中,i表示读取数据量的大小
i = inputStream.read(buffer);
//遍历字节数组,将数组中的数据追加到content中
for(int j=0; j<i; j++) {
content.append((char) buffer[j]);
}
//System.out.println(content.toString());
//截取客户端请求的资源路径
parseUrl(content.toString());
}
//截取客户端请求的资源路径
private static void parseUrl(String content) {
//定义2个变量,存放请求后2个空格位置
int index1, index2;
index1 = content.indexOf(" ");
if(index1 != -1) {
index2 = content.indexOf(" ", index1+1);
if(index2 > index1) {
url = content.substring(index1+2, index2);
}
}
System.out.println(url);
}
}
主要的就是main函数中的逻辑,通过SocketServer绑定一个特定的端口从而可以接收请求,接收请求后判断该请求是动态还是静态请求,然后根据请求的内容调用特定的html或者java代码,得到需要响应的内容,组织成http响应报文,返回浏览器即可。