名稱(chēng)∶Minicat
Minicat要做的事情∶作為一個(gè)服務(wù)器軟件提供服務(wù)的,也即我們可以通過(guò)瀏覽器客戶(hù)端發(fā)送http請(qǐng)求,Minicat可以接收到請(qǐng)求進(jìn)行處理,處理之后的結(jié)果可以返回瀏覽器客戶(hù)端。
1)提供服務(wù),接收請(qǐng)求(Socket通信)
2)請(qǐng)求信息封裝成Request對(duì)象(Response對(duì)象)
3)客戶(hù)端請(qǐng)求資源,資源分為靜態(tài)資源(html)和動(dòng)態(tài)資源(Servlet )
4)資源返回給客戶(hù)端瀏覽器
我們遞進(jìn)式完成以上需求,提出V1.0、V2.0、V3.0版本的需求
V1.0需求∶瀏覽器請(qǐng)求nttp/localhost8080返回一個(gè)固定的字符串到頁(yè)面"Hello Minicat!"
V2.0需求∶封裝Request和Response對(duì)象,返回html靜態(tài)資源文件
V3.0需求∶可以請(qǐng)求動(dòng)態(tài)資源(Servlet)完成上述三個(gè)版本后,我們的代碼如下
- Bootstrap 啟動(dòng)類(lèi)
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 的主類(lèi)
*/
public class Bootstrap {
/**定義socket監(jiān)聽(tīng)的端口號(hào)*/
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 {
// 加載解析相關(guān)的配置,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版本
需求:瀏覽器請(qǐng)求http://localhost:8080,返回一個(gè)固定的字符串到頁(yè)面"Hello Minicat!"
*/
/*while (true){
Socket socket = serverSocket.accept();
// 有了socket , 接收到請(qǐng)求,獲取輸出流
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對(duì)象,返回html靜態(tài)資源文件
*/
/*while (true){
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
// 封裝Request對(duì)象和Response對(duì)象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
String url = request.getUrl();
response.outputHtml(url);
}*/
/**
* 完成Minicat 3.0版本
* 需求:可以請(qǐng)求動(dòng)態(tài)資源(Servlet)
*/
/*while (true){
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
// 封裝Request對(duì)象和Response對(duì)象
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 {
// 動(dòng)態(tài)資源servlet請(qǐng)求
map.get(url).service(request,response);
}
socket.close();
}*/
/**
* 多線(xiàn)程改造(不使用線(xiàn)程池)
*/
/*while (true){
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new RequestProcessor(socket, map);
requestProcessor.start();
}*/
System.out.println("=========>>>>>>使用線(xiàn)程池進(jìn)行多線(xiàn)程改造");
/*
多線(xiàn)程改造(使用線(xiàn)程池)
*/
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 的啟動(dòng)入口
*/
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
try {
// 啟動(dòng) minicat
bootstrap.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- Http協(xié)議工具類(lèi)
package com.study.server;
/**
* http協(xié)議工具類(lèi),主要是提供響應(yīng)頭信息,這里我們只提供200和404的情況
* @author Qi XueSong
*/
public class HttpProtocolUtil {
/**
* 為響應(yīng)碼200提供請(qǐng)求頭信息
*/
public static String getHttpHeader200(long contentLength){
return "HTTP/1.1 200 OK \n" +
"Content-Type: text/html \n" +
"Content-Length: " + contentLength + " \n" +
"\r\n";
}
/**
* 為響應(yīng)碼404提供請(qǐng)求頭信息(此處也包含了數(shù)據(jù)內(nèi)容)
*/
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封裝類(lèi)
package com.study.server;
import java.io.IOException;
import java.io.InputStream;
/**
* 把請(qǐng)求信息封裝為Request對(duì)象(根據(jù)InputSteam輸入流封裝)
* @author Qi XueSong
*/
public class Request {
private String method; // 請(qǐng)求方式,比如GET/POST
private String url; // 例如 /,/index.html
private InputStream inputStream; // 輸入流,其他屬性從輸入流中解析出來(lái)
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() {
}
// 構(gòu)造器,輸入流傳入
public Request(InputStream inputStream) throws IOException {
this.inputStream = inputStream;
// 從輸入流中獲取請(qǐng)求信息
int count = 0;
while (count == 0) {
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
String inputStr = new String(bytes);
// 獲取第一行請(qǐng)求頭信息
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封裝類(lèi)
package com.study.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* 封裝Response對(duì)象,需要依賴(lài)于OutputStream
* 該對(duì)象需要提供核心方法,輸出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來(lái)獲取到靜態(tài)資源的絕對(duì)路徑,進(jìn)一步根據(jù)絕對(duì)路徑讀取該靜態(tài)資源文件,最終通過(guò)
* 輸出流輸出
* /-----> classes
*/
public void outputHtml(String path) throws IOException {
// 獲取靜態(tài)資源文件的絕對(duì)路徑
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)資源請(qǐng)求處理工具類(lèi)
package com.study.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class StaticResourceUtil {
/**
* 獲取靜態(tài)資源文件的絕對(duì)路徑
* @author Qi XueSong
*/
public static String getAbsolutePath(String path) {
String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
return absolutePath.replaceAll("\\\\","/") + path;
}
/**
* 讀取靜態(tài)資源文件輸入流,通過(guò)輸出流輸出
*/
public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
int count = 0;
while(count == 0) {
count = inputStream.available();
}
int resourceSize = count;
// 輸出http請(qǐng)求頭,然后再輸出具體內(nèi)容
outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
// 讀取內(nèi)容輸出
long written = 0 ;// 已經(jīng)讀取的內(nèi)容長(zhǎng)度
int byteSize = 1024; // 計(jì)劃每次緩沖的長(zhǎng)度
byte[] bytes = new byte[byteSize];
while(written < resourceSize) {
if(written + byteSize > resourceSize) { // 說(shuō)明剩余未讀取大小不足一個(gè)1024長(zhǎng)度,那就按真實(shí)長(zhǎng)度處理
byteSize = (int) (resourceSize - written); // 剩余的文件內(nèi)容長(zhǎng)度
bytes = new byte[byteSize];
}
inputStream.read(bytes);
outputStream.write(bytes);
outputStream.flush();
written+=byteSize;
}
inputStream.close();
outputStream.close();
}
}
動(dòng)態(tài)資源請(qǐng)求
- 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抽象類(lèi)定義
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è)務(wù)類(lèi)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 {
}
}
- 多線(xiàn)程改造封裝的RequestProcessor類(lèi)
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對(duì)象和Response對(duì)象
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 {
// 動(dòng)態(tài)資源servlet請(qǐng)求
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>