静态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>