名稱∶Minicat
Minicat要做的事情∶作為一個服務器軟件提供服務的竭望,也即我們可以通過瀏覽器客戶端發(fā)送http請求,Minicat可以接收到請求進行處理是复,處理之后的結果可以返回瀏覽器客戶端删顶。
1)提供服務,接收請求(Socket通信)
2)請求信息封裝成Request對象(Response對象)
3)客戶端請求資源淑廊,資源分為靜態(tài)資源(html)和動態(tài)資源(Servlet )
4)資源返回給客戶端瀏覽器
我們遞進式完成以上需求逗余,提出V1.0、V2.0季惩、V3.0版本的需求
V1.0需求∶瀏覽器請求nttp/localhost8080返回一個固定的字符串到頁面"Hello Minicat!"
V2.0需求∶封裝Request和Response對象录粱,返回html靜態(tài)資源文件
V3.0需求∶可以請求動態(tài)資源(Servlet)完成上述三個版本后,我們的代碼如下
- Bootstrap 啟動類
package com.study.server;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
/**
* @author Qi XueSong
* Minicat 的主類
*/
public class Bootstrap {
/**定義socket監(jiān)聽的端口號*/
private int port = 8080;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
Map<String,HttpServlet> map = new HashMap<>();
private void start() throws Exception {
// 加載解析相關的配置画拾,web.xml
loadServlet();
int corePoolSize = 10;
int maximumPoolSize = 50;
long keepAliveTime = 100L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("Minicat start on port:" + port);
/*
完成Minicat 1.0版本
需求:瀏覽器請求http://localhost:8080,返回一個固定的字符串到頁面"Hello Minicat!"
*/
/*while (true){
Socket socket = serverSocket.accept();
// 有了socket , 接收到請求啥繁,獲取輸出流
OutputStream outputStream = socket.getOutputStream();
String data = "Hello Minicat!";
String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length)+data;
outputStream.write(responseText.getBytes());
socket.close();
}*/
/**
* 完成Minicat 2.0版本
* 需求:封裝Request和Response對象,返回html靜態(tài)資源文件
*/
/*while (true){
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
// 封裝Request對象和Response對象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
String url = request.getUrl();
response.outputHtml(url);
}*/
/**
* 完成Minicat 3.0版本
* 需求:可以請求動態(tài)資源(Servlet)
*/
/*while (true){
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
// 封裝Request對象和Response對象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
String url = request.getUrl();
// 靜態(tài)資源處理
if(map.get(request.getUrl()) == null){
response.outputHtml(url);
} else {
// 動態(tài)資源servlet請求
map.get(url).service(request,response);
}
socket.close();
}*/
/**
* 多線程改造(不使用線程池)
*/
/*while (true){
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new RequestProcessor(socket, map);
requestProcessor.start();
}*/
System.out.println("=========>>>>>>使用線程池進行多線程改造");
/*
多線程改造(使用線程池)
*/
while (true){
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new RequestProcessor(socket, map);
threadPoolExecutor.execute(requestProcessor);
}
}
private void loadServlet() {
InputStream resourceAsStream = Bootstrap.class.getClassLoader().getResourceAsStream("web.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("http://servlet");
for (int i = 0; i < list.size(); i++) {
Element element = list.get(i);
// <servlet-name>study</servlet-name>
Element servletNameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletNameElement.getStringValue();
// <servlet-class>com.study.server.StudyServlet</servlet-class>
Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
String servletClass = servletclassElement.getStringValue();
// 根據(jù)servlet-name的值找到url-pattern
Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
map.put(urlPattern,(HttpServlet) Class.forName(servletClass).newInstance());
}
} catch (DocumentException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* minicat 的啟動入口
*/
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
try {
// 啟動 minicat
bootstrap.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- Http協(xié)議工具類
package com.study.server;
/**
* http協(xié)議工具類青抛,主要是提供響應頭信息旗闽,這里我們只提供200和404的情況
* @author Qi XueSong
*/
public class HttpProtocolUtil {
/**
* 為響應碼200提供請求頭信息
*/
public static String getHttpHeader200(long contentLength){
return "HTTP/1.1 200 OK \n" +
"Content-Type: text/html \n" +
"Content-Length: " + contentLength + " \n" +
"\r\n";
}
/**
* 為響應碼404提供請求頭信息(此處也包含了數(shù)據(jù)內容)
*/
public static String getHttpHeader404(){
String str404 = "<h1>404 NOT Found</h1>";
return "HTTP/1.1 404 NOT Found \n" +
"Content-Type: text/html \n" +
"Content-Length: " + str404.getBytes().length + " \n" +
"\r\n" + str404;
}
}
- Request封裝類
package com.study.server;
import java.io.IOException;
import java.io.InputStream;
/**
* 把請求信息封裝為Request對象(根據(jù)InputSteam輸入流封裝)
* @author Qi XueSong
*/
public class Request {
private String method; // 請求方式,比如GET/POST
private String url; // 例如 /,/index.html
private InputStream inputStream; // 輸入流蜜另,其他屬性從輸入流中解析出來
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public Request() {
}
// 構造器适室,輸入流傳入
public Request(InputStream inputStream) throws IOException {
this.inputStream = inputStream;
// 從輸入流中獲取請求信息
int count = 0;
while (count == 0) {
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
String inputStr = new String(bytes);
// 獲取第一行請求頭信息
String firstLineStr = inputStr.split("\\n")[0]; // GET / HTTP/1.1
String[] strings = firstLineStr.split(" ");
this.method = strings[0];
this.url = strings[1];
System.out.println("=====>>method:" + method);
System.out.println("=====>>url:" + url);
}
}
- Response封裝類
package com.study.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* 封裝Response對象,需要依賴于OutputStream
* 該對象需要提供核心方法举瑰,輸出html
*
* @author Qi XueSong
*/
public class Response {
private OutputStream outputStream;
public Response() {
}
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
// 使用輸出流輸出指定字符串
public void output(String content) throws IOException {
outputStream.write(content.getBytes());
}
/**
*
* @param path url捣辆,隨后要根據(jù)url來獲取到靜態(tài)資源的絕對路徑,進一步根據(jù)絕對路徑讀取該靜態(tài)資源文件此迅,最終通過
* 輸出流輸出
* /-----> classes
*/
public void outputHtml(String path) throws IOException {
// 獲取靜態(tài)資源文件的絕對路徑
String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
// 輸入靜態(tài)資源文件
File file = new File(absoluteResourcePath);
if(file.exists() && file.isFile()) {
// 讀取靜態(tài)資源文件罪帖,輸出靜態(tài)資源
StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
}else{
// 輸出404
output(HttpProtocolUtil.getHttpHeader404());
}
}
}
- 靜態(tài)資源請求處理工具類
package com.study.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class StaticResourceUtil {
/**
* 獲取靜態(tài)資源文件的絕對路徑
* @author Qi XueSong
*/
public static String getAbsolutePath(String path) {
String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
return absolutePath.replaceAll("\\\\","/") + path;
}
/**
* 讀取靜態(tài)資源文件輸入流,通過輸出流輸出
*/
public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
int count = 0;
while(count == 0) {
count = inputStream.available();
}
int resourceSize = count;
// 輸出http請求頭,然后再輸出具體內容
outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
// 讀取內容輸出
long written = 0 ;// 已經讀取的內容長度
int byteSize = 1024; // 計劃每次緩沖的長度
byte[] bytes = new byte[byteSize];
while(written < resourceSize) {
if(written + byteSize > resourceSize) { // 說明剩余未讀取大小不足一個1024長度邮屁,那就按真實長度處理
byteSize = (int) (resourceSize - written); // 剩余的文件內容長度
bytes = new byte[byteSize];
}
inputStream.read(bytes);
outputStream.write(bytes);
outputStream.flush();
written+=byteSize;
}
inputStream.close();
outputStream.close();
}
}
動態(tài)資源請求
- Servlet接口定義
package com.study.server;
public interface Servlet {
void init() throws Exception;
void destory() throws Exception;
void service(Request request, Response response) throws Exception;
}
- HttpServlet抽象類定義
package com.study.server;
/**
* @author Qi XueSong
*/
public abstract class HttpServlet implements Servlet{
public abstract void doGet(Request request, Response response);
public abstract void doPost(Request request, Response response);
@Override
public void service(Request request, Response response) throws Exception {
if("GET".equals(request.getMethod())){
doGet(request,response);
} else {
doPost(request,response);
}
}
}
- 業(yè)務類Servlet定義StudyServlet
package com.study.server;
import java.io.IOException;
/**
* @author Qi XueSong
*/
public class StudyServlet extends HttpServlet {
@Override
public void doGet(Request request, Response response) {
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String content = "<h1>StudyServlet get</h1>";
try {
response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(Request request, Response response) {
String content = "<h1>StudyServlet post</h1>";
try {
response.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length)+content);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void init() throws Exception {
}
@Override
public void destory() throws Exception {
}
}
- 多線程改造封裝的RequestProcessor類
package com.study.server;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.Map;
/**
* @author qixuesong
* @version 1.0
* @since 2020/10/7
*/
public class RequestProcessor extends Thread {
private Socket socket;
private Map<String,HttpServlet> map;
public RequestProcessor(Socket socket, Map<String, HttpServlet> map) {
this.socket = socket;
this.map = map;
}
public RequestProcessor() {
}
@Override
public void run() {
try {
InputStream inputStream = socket.getInputStream();
// 封裝Request對象和Response對象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
String url = request.getUrl();
// 靜態(tài)資源處理
if(!map.containsKey(request.getUrl())){
response.outputHtml(url);
} else {
// 動態(tài)資源servlet請求
map.get(url).service(request,response);
}
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
<servlet>
<servlet-name>study</servlet-name>
<servlet-class>com.study.server.StudyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>study</servlet-name>
<url-pattern>/study</url-pattern>
</servlet-mapping>
</web-app>