静态web资源服务器
把本地上面的资源共享出来,使用socket
版本一:校验文件并输出
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; /** * @ClassName MainServer * @Description: * 1.启动一个程序,持续去监听某一端口号 * 2.获取客户端提交过来的信息(如果使用浏览器来发送,HTTP请求报文) * 3.解析请求资源 * 4.尝试在服务器硬盘上面去查找该文件,如果找到,则写入到响应体中;如果没有找到,则写入404 * @Author 远志 zhangsong@cskaoyan.onaliyun.com * @Date 2021/12/6 11:51 * @Version V1.0 **/ public class MainServer { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(8080); while (true){ //其实就是java语言中对于tcp连接的封装 Socket client = serverSocket.accept(); //客户端发送过来的全部都是文本数据 //SocketInputStream读取的时候,读取到末尾会阻塞住,不会立刻返回-1,一段时间没有读取到,才会最终返回-1 InputStream inputStream = client.getInputStream(); byte[] bytes = new byte[1024]; int length = inputStream.read(bytes); String content = new String(bytes, 0, length); System.out.println(content); } } catch (IOException e) { e.printStackTrace(); } } }
版本二:解析请求报文并封装进一个request对象
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; /** * @ClassName MainServer * @Description: * 1.启动一个程序,持续去监听某一端口号 * 2.获取客户端提交过来的信息(如果使用浏览器来发送,HTTP请求报文) * 3.解析请求资源 * 4.尝试在服务器硬盘上面去查找该文件,如果找到,则写入到响应体中;如果没有找到,则写入404 * @Author 远志 zhangsong@cskaoyan.onaliyun.com * @Date 2021/12/6 11:51 * @Version V1.0 **/ public class MainServer { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(8080); while (true){ //其实就是java语言中对于tcp连接的封装 Socket client = serverSocket.accept(); //客户端发送过来的全部都是文本数据 //SocketInputStream读取的时候,读取到末尾会阻塞住,不会立刻返回-1,一段时间没有读取到,才会最终返回-1 Request request = new Request(client); //request.getHeaderNames() } } catch (IOException e) { e.printStackTrace(); } } }
import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @ClassName Request * @Description: 把请求报文封装到request对象中 * @Author 远志 zhangsong@cskaoyan.onaliyun.com * @Date 2021/12/6 14:43 * @Version V1.0 **/ public class Request { private String requestString; /** * 请求方法 */ private String method; /** * 请求资源 */ private String requestURI; /** * 版本协议 */ private String protocol; private Map<String, String> requestHeaders = new HashMap<>(); public Request(Socket client) { try { InputStream inputStream = client.getInputStream(); byte[] bytes = new byte[1024]; int length = inputStream.read(bytes); this.requestString = new String(bytes, 0, length); parseRequestLine(); parseRequestHeaders(); } catch (IOException e) { e.printStackTrace(); } } /** * * * @title: * @createAuthor: 远志 * @createDate: 2021/12/6 14:55 * @description: 解析请求头 * @version: 1.0 * @return: */ private void parseRequestHeaders() { int begin = requestString.indexOf("\r\n"); int end = requestString.indexOf("\r\n\r\n"); String substring = requestString.substring(begin + 2, end); String[] parts = substring.split("\r\n"); for (String part : parts) { int i = part.indexOf(":"); String headerName = part.substring(0, i).trim(); String headerValue = part.substring(i + 1).trim(); requestHeaders.put(headerName, headerValue); } } /** * * * @title: * @createAuthor: 远志 * @createDate: 2021/12/6 14:46 * @description: * 解析请求行 * 将请求报文进行拆解,拆分成若干部分 * 利用换行符来进行拆分\r\n * 0-\r\n 请求行 * \r\n-----\r\n\r\n 请求头 * @version: 1.0 * @return: */ private void parseRequestLine() { //拿到第一次出现\r\n的下标位置 int index = requestString.indexOf("\r\n"); String requestLine = requestString.substring(0, index); String[] parts = requestLine.split(" "); this.method = parts[0]; //如果浏览器是以get请求方法发送请求,同时携带了请求参数,那么请求参数会附着在uri中 //此时如果不把参数去掉,则找不到该文件 this.requestURI = parts[1]; this.protocol = parts[2]; //主要用来判断请求资源中是否有请求参数 //正常情况下,我们还需要进一步去处理请求参数,我们这里面为了简便,就不去处理了 int i = requestURI.indexOf("?"); if(i != -1){ requestURI = requestURI.substring(0, i); } } public String getMethod() { return method; } public String getRequestURI() { return requestURI; } public String getProtocol() { return protocol; } public String getHeader(String headerName){ return requestHeaders.get(headerName); } public Set<String> getHeaderNames(){ return requestHeaders.keySet(); } }
此时存在一个问题,socketInputstream在读取完毕后不会立刻返回-1,会阻塞一段时间再返回-1,这里要注意使用监听端口获得的inputstream并不是我们常用的FileInputStream,所以才会有这种问题,于是需要采用多线程来解决
import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * @ClassName MainServer * @Description: * 1.启动一个程序,持续去监听某一端口号 * 2.获取客户端提交过来的信息(如果使用浏览器来发送,HTTP请求报文) * 3.解析请求资源 * 4.尝试在服务器硬盘上面去查找该文件,如果找到,则写入到响应体中;如果没有找到,则写入404 * @Author 远志 zhangsong@cskaoyan.onaliyun.com * @Date 2021/12/6 11:51 * @Version V1.0 **/ public class MainServer { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(8080); while (true){ //其实就是java语言中对于tcp连接的封装 Socket client = serverSocket.accept(); //客户端发送过来的全部都是文本数据 //SocketInputStream读取的时候,读取到末尾会阻塞住,不会立刻返回-1,一段时间没有读取到,才会最终返回-1 //专门开启一个子线程来去处理请求的拆解步骤,主线程只负责去接收客户端的连接 new Thread(new Runnable() { @Override public void run() { Request request = new Request(client); // /1.html /2.html //如果要把本地硬盘上面去查找文件,那么一定需要用file String requestURI = request.getRequestURI(); File file = new File(requestURI.substring(1)); OutputStream outputStream = null; try { outputStream = client.getOutputStream(); StringBuffer buffer = new StringBuffer(); if(file.exists() && file.isFile()){ //确保文件的确存在,并且不是目录 //状态码应当返回200 buffer.append("HTTP/1.1 200 OK\r\n"); buffer.append("Content-Type:text/html\r\n"); buffer.append("Server: agou\r\n"); buffer.append("\r\n"); //此时吧响应行、响应头、空行写出去了 outputStream.write(buffer.toString().getBytes("utf-8")); //最后在写响应体 FileInputStream fileInputStream = new FileInputStream(file); byte[] bytes = new byte[1024]; int length = 0; while ((length = fileInputStream.read(bytes)) != -1){ outputStream.write(bytes,0, length); } return; } buffer.append("HTTP/1.1 404 Not Found\r\n"); buffer.append("Content-Type:text/html\r\n"); buffer.append("\r\n"); buffer.append("<div style='color:red'>File Not Found<div>"); outputStream.write(buffer.toString().getBytes("utf-8")); } catch (IOException e) { e.printStackTrace(); }finally { if(outputStream != null){ try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }).start(); } } catch (IOException e) { e.printStackTrace(); } } }
import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @ClassName Request * @Description: 把请求报文封装到request对象中 * @Author 远志 zhangsong@cskaoyan.onaliyun.com * @Date 2021/12/6 14:43 * @Version V1.0 **/ public class Request { private String requestString; /** * 请求方法 */ private String method; /** * 请求资源 */ private String requestURI; /** * 版本协议 */ private String protocol; private Map<String, String> requestHeaders = new HashMap<>(); public Request(Socket client) { try { InputStream inputStream = client.getInputStream(); byte[] bytes = new byte[1024]; int length = inputStream.read(bytes); this.requestString = new String(bytes, 0, length); parseRequestLine(); parseRequestHeaders(); } catch (IOException e) { e.printStackTrace(); } } /** * * * @title: * @createAuthor: 远志 * @createDate: 2021/12/6 14:55 * @description: 解析请求头 * @version: 1.0 * @return: */ private void parseRequestHeaders() { int begin = requestString.indexOf("\r\n"); int end = requestString.indexOf("\r\n\r\n"); String substring = requestString.substring(begin + 2, end); String[] parts = substring.split("\r\n"); for (String part : parts) { int i = part.indexOf(":"); String headerName = part.substring(0, i).trim(); String headerValue = part.substring(i + 1).trim(); requestHeaders.put(headerName, headerValue); } } /** * * * @title: * @createAuthor: 远志 * @createDate: 2021/12/6 14:46 * @description: * 解析请求行 * 将请求报文进行拆解,拆分成若干部分 * 利用换行符来进行拆分\r\n * 0-\r\n 请求行 * \r\n-----\r\n\r\n 请求头 * @version: 1.0 * @return: */ private void parseRequestLine() { //拿到第一次出现\r\n的下标位置 int index = requestString.indexOf("\r\n"); String requestLine = requestString.substring(0, index); String[] parts = requestLine.split(" "); this.method = parts[0]; //如果浏览器是以get请求方法发送请求,同时携带了请求参数,那么请求参数会附着在uri中 //此时如果不把参数去掉,则找不到该文件 this.requestURI = parts[1]; this.protocol = parts[2]; //主要用来判断请求资源中是否有请求参数 //正常情况下,我们还需要进一步去处理请求参数,我们这里面为了简便,就不去处理了 int i = requestURI.indexOf("?"); if(i != -1){ requestURI = requestURI.substring(0, i); } } public String getMethod() { return method; } public String getRequestURI() { return requestURI; } public String getProtocol() { return protocol; } public String getHeader(String headerName){ return requestHeaders.get(headerName); } public Set<String> getHeaderNames(){ return requestHeaders.keySet(); } }
服务器
JavaEE规范:
即JavaEE给服务器的开发者制定了一套接口,为了保证服务器可以进行解耦,在替换的时候方便进行替换,于是所有服务器都需要实现JavaEE制定的标准和接口,这样在使用的时候就不必进行大量的替换操作
Tomcat
安装
bin-启动的目录,根目录
conf-对tomcat进行配置
logs-日志存放目录:可以根据最后的修改时间来排查故障
webapps目录-部署资源
启动
1.bin目录下执行startup.bat文件
2.在bin目录下唤出cmd,执行startup
部署资源
直接部署
直接将资源放在tomcat的webapps目录下
注意:在tomcat中,不管是直接部署还是虚拟映射,tomct的最小资源单位是应用,所以需要将文件放在一个应用中。相当于是每个文件都需要一个包名,tomcat根据包名来进行识别
访问的方法:当输入http://localhost:8080时,此时相当于已经定位到了tomcat的webapps目录,接下来只需要写出相对路径关系即可,即使用应用名来访问。
例如:http://localhost:8080/36th/1.txt
除此之外,可以部署一个war包,tomcat会自动解压形成目录,相当于jar包的形式,本质上还是个压缩包
虚拟映射
不把文件放在webapps目录下,随意放置;
本质上客户端是从服务器的磁盘中获取内容,所以理论上来说所有的磁盘路径都可以读取到
1.在conf/Catalina/localhost目录下,配置一个xml文件
<?xml version="1.0" encoding="UTF-8"?> <Context docBase="D:/app" />
tomcat的最小单位是应用,而应用有两个属性:应用名与路径
Context path来表示应用名 docBase 应用的路径
配置完虚拟映射后相当于将webapps的目录换成了我们自定义的目录,那我们如果直接输入localhost就相当于定位到了该目录,此时访问资源就只需要相对于这个目录来访问即可
2.conf/server.xml文件中配置Context节点(了解)
Host节点下配置Context节点
<Context path="/app362" docBase="D:\app" />
不推荐使用,因为修改了主配置文件容易产生问题
Tomcat组件
tomcat本身是由一系列的可插拔的组件组成的,主要是在server.xml文件中配置,tomcat在启动的时候会自动读取xml文件里面的内容,将每个节点依次解析为一个组件(对象)
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
以Connector为例,tomcat在启动时,会读取xml文件里面的配置,根据这些配置信息实例化一个Connector对象出来,该对象会监听8080端口号,主要的职责就是将HTTP/1.1协议的请求报文解析成为request对象
Tomcat请求处理流程
以访问http://localhost:8080/app36/1.txt为例
1.地址栏输入对应的地址,首先进行域名的解析(浏览器、操作系统、hosts、DNS服务器)拿到ip地址
2.TCP三次握手建立连接
3.浏览器生成请求报文,经过tcp层拆包,打上tcp头标签,经过ip层打上ip标签,本机和目标的ip地址端口号
4.从链路层出去,在网络上进行中转传输,到达目标主机后先经过ip层拆掉标签,然后经过tcp层拆掉标签完成对源文件的重新组装
5.HTTP报文被一直监听8080端口的Connector接收到,将报文封装成request对象,同时提供一共response对象,用于返回响应报文
6.Connector对象将这两个对象传给Engine,engin进一步下发给Host来对对象进行处理
7.Host的职责是挑选一个合适的Context,尝试去找app36的应用(webapps、conf/Catalina/localhost、server.xml),如果找到了就将这两个对象进一步交给应用来处理,如果没有找到会执行ROOT下的默认文件
8.到达应用之后,有效的路径是/1.txt,利用docBase+/1.txt查找该文件是否存在;找到则将该文件的内容写入到response中,找不到则写入404,这里要注意即使是返回404,我们也认为这次返回是一次有效的返回
9.Connector读取response中的数据,按照HTTP/1.1的格式要求生成响应报文
…
这里注意:如果文件在tocmat的webapps目录里面,应用名(path)会取目录的名称, 应用的路径(docBase):webapps路径 + 应用名
Tomcat的设置
1.设置端口号
有些网站在访问的时候是不用带端口号的,这是因为他们使用的是当前协议默认的端口号
http协议是80端口号
https协议是443端口号
需要设置tomcat监听80端口号
<Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
2.设置默认访问的应用
tomcat中有一个缺省的应用,如果找不到其他应用了,就会交给缺省的来处理
应用名叫做ROOT
在访问的时候不需要加应用名就能访问
比如ROOT应用下有一个1.txt,那么访问http://localhost:8080/1.txt
http://localhost:8080/app37/1.txt,将其交给ROOT应用,然后在该应用中去查找/app37/1.txt文件
如果需要访问资源并且不携带应用名,可以直接设置当前资源所在的应用为ROOT
1.直接部署,将应用名改为ROOT,原来的ROOT换一个名字
2.虚拟映射,创建一个xml文件,将名字改成ROOT.xml即可,会覆盖webapps里的ROOT
3.设置欢迎页面
当最终的访问地址是一个目录而不是具体的文件的时候,tomcat会按照如下的顺序依次去查找文件,如果找到则加载,如果找不到就返回一个404
无论如何,最终我们解析到的都是一个硬盘上的目录,那么如果只输入localhost的话,最后就只会定义到ROOT目录下,但ROOT目录下有很多个资源,于是就会根据以下的设置来进行访问
<welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list>