【BIO】基于BIO實(shí)現(xiàn)簡(jiǎn)單動(dòng)態(tài)HTTP服務(wù)器
-
需求
-
支持瀏覽器客戶端接入 -
根據(jù)請(qǐng)求的資源路徑響應(yīng)正確的結(jié)果 -
支持訪問(wèn)靜態(tài)資源 -
支持訪問(wèn)動(dòng)態(tài)資源 -
當(dāng)資源不存在時(shí)響應(yīng) 404
提示 -
當(dāng)發(fā)生異常時(shí)提示 500
錯(cuò)誤 -
為保證服務(wù)器安全穩(wěn)定,服務(wù)器端不可無(wú)限開(kāi)啟新線程 -
思路
-
啟動(dòng)ServerSocket,監(jiān)聽(tīng)指定端口 -
等待客戶端接入,將接入的客戶端交給線程池去處理,主線程繼續(xù)監(jiān)聽(tīng)客戶端接入 -
靜態(tài)資源:從指定的靜態(tài)資源路徑去查找文件,將文件轉(zhuǎn)換為字節(jié),寫(xiě)入輸出流 -
動(dòng)態(tài)資源:從類路徑下查找響應(yīng)的Servlet,調(diào)用Servlet的service處理程序,將返回值寫(xiě)入輸出流 -
當(dāng)請(qǐng)求的資源不存在,將 404.html
文件寫(xiě)入輸出流 -
當(dāng)發(fā)生異常,將 500.html
文件寫(xiě)入輸出流
在實(shí)現(xiàn)HTTP服務(wù)器之前,我們需要先來(lái)了解一下HTTP的報(bào)文結(jié)構(gòu)。
# HTTP報(bào)文結(jié)構(gòu)
-
可參考
-
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Messages -
Request與Response的報(bào)文結(jié)構(gòu)
-
Request的報(bào)文結(jié)構(gòu)
-
所以按照約定的報(bào)文格式進(jìn)行消息的解析與發(fā)送即可~
# 資源準(zhǔn)備
-
靜態(tài)資源
-
動(dòng)態(tài)資源,Servlet
-
Servlet規(guī)范
/**
* Servlet規(guī)范接口
*
* @author futao
* @date 2020/7/6
*/
public interface Servlet {
/**
* 業(yè)務(wù)處理程序
*
* @return 響應(yīng)
*/
Object service();
}
-
Servlet服務(wù)端小程序
/**
* 返回字符串
*
* @author futao
* @date 2020/7/6
*/
public class HelloServlet implements Servlet {
@Override
public Object service() {
return "greet from dynamic server...";
}
}
--------------------------------------------------------
--------------------------------------------------------
/**
* 返回list集合
*
* @author futao
* @date 2020/7/6
*/
public class UserListServlet implements Servlet {
@Override
public Object service() {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(
User.builder()
.name("喜歡天文的pony站長(zhǎng)")
.age(i)
.address("浙江杭州")
.build()
);
}
return users;
}
@Getter
@Setter
@Builder
static class User {
private String name;
private int age;
private String address;
}
}
--------------------------------------------------------
--------------------------------------------------------
/**
* 返回當(dāng)前時(shí)間
*
* @author futao
* @date 2020/7/6
*/
public class CurTimeServlet implements Servlet {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public Object service() {
return "當(dāng)前時(shí)間為: " + DATE_TIME_FORMATTER.format(LocalDateTime.now(ZoneOffset.ofHours(8)));
}
}
--------------------------------------------------------
--------------------------------------------------------
/**
* 模擬異常的Servlet
*
* @author futao
* @date 2020/7/6
*/
public class ExceptionServlet implements Servlet {
@Override
public Object service() {
throw new RuntimeException("發(fā)生了異常");
}
}
# 服務(wù)器代碼編寫(xiě)
-
核心代碼:
/**
* 基于BIO實(shí)現(xiàn)的靜態(tài) and 動(dòng)態(tài)服務(wù)器
*
* @author futao
* @date 2020/7/6
*/
public class BIODynamicServer {
private static final Logger logger = LoggerFactory.getLogger(BIODynamicServer.class);
/**
* 用于處理客戶端接入的線程池
*/
private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(10);
/**
* 靜態(tài)資源路徑
*/
private static final String STATIC_RESOURCE_PATH = System.getProperty("user.dir") + "/practice/src/main/resources/pages/";
/**
* Servlet的類路徑
*/
private static final String DYNAMIC_RESOURCE_CLASS_PATH = "com.futao.practice.chatroom.bio.v6server.servlet.";
/**
* Servlet后綴
*/
private static final String SERVLET_SUFFIX = "Servlet";
/**
* Servlet緩存
*/
private static final Map<String, Servlet> SERVLET_MAP = new HashMap<>();
/**
* 默認(rèn)頁(yè)面
*/
private static final String DEFAULT_PAGE = STATIC_RESOURCE_PATH + "index.html";
/**
* 響應(yīng)的基礎(chǔ)信息
*/
public static final String BASIC_RESPONSE = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n" +
"Vary: Accept-Encoding\r\n";
/**
* 回車換行符
*/
private static final String carriageReturn = "\r\n";
public void start() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(Constants.SERVER_PORT);
logger.debug("========== 基于BIO實(shí)現(xiàn)的服務(wù)器,開(kāi)始提供服務(wù) ==========");
while (true) {
Socket socket = serverSocket.accept();
THREAD_POOL.execute(() -> {
try {
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
byte[] bytes = new byte[1024];
int curByteLength = inputStream.read(bytes);
byte[] dest = new byte[curByteLength];
System.arraycopy(bytes, 0, dest, 0, curByteLength);
//請(qǐng)求報(bào)文
String request = new String(dest);
logger.info("接收到客戶端的數(shù)據(jù):\n{}\n{}", request, StringUtils.repeat("=", 50));
// 解析請(qǐng)求地址
String requestUri = BIODynamicServer.getRequestUri(request);
// 靜態(tài)資源處理器
boolean staticHandler = staticHandler(requestUri, outputStream);
if (!staticHandler) {
//動(dòng)態(tài)資源處理器
if (!dynamicHandler(requestUri, outputStream)) {
//動(dòng)態(tài)資源不存在,響應(yīng)404
logger.debug("資源[{}]不存在,響應(yīng)404", requestUri);
staticHandler("404.html", outputStream);
}
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
-
解析請(qǐng)求地址
/**
* 獲取請(qǐng)求的資源地址
*
* @param request
* @return
*/
private static String getRequestUri(String request) {
//GET /index.html HTTP/1.1
int firstBlank = request.indexOf(" ");
String excludeMethod = request.substring(firstBlank + 2);
return excludeMethod.substring(0, excludeMethod.indexOf(" "));
}
-
處理靜態(tài)資源
/**
* 靜態(tài)資源處理器
*
* @return
*/
public boolean staticHandler(String page, OutputStream outputStream) throws IOException {
//資源的絕對(duì)路徑
String filePath = BIODynamicServer.STATIC_RESOURCE_PATH + page;
boolean fileExist = false;
File file = new File(filePath);
if (file.exists() && file.isFile()) {
logger.debug("靜態(tài)資源[{}]存在", page);
fileExist = true;
//讀取文件內(nèi)容
byte[] bytes = Files.readAllBytes(Paths.get(filePath));
//寫(xiě)入響應(yīng)
BIODynamicServer.writeResponse(outputStream, bytes);
}
return fileExist;
}
-
處理動(dòng)態(tài)資源
/**
* 動(dòng)態(tài)資源處理器
*
* @param requestUri 請(qǐng)求資源名
* @param outputStream 輸出流
* @return
* @throws IOException
*/
private boolean dynamicHandler(String requestUri, OutputStream outputStream) throws IOException {
//Servlet是否存在
boolean servletExist = false;
//Servlet
Servlet servletInstance = null;
//從緩存中取
Servlet servlet = SERVLET_MAP.get(requestUri);
if (servlet == null) {
//緩存中不存在
try {
//反射獲取Class
Class<Servlet> aClass = (Class<Servlet>) Class.forName(BIODynamicServer.DYNAMIC_RESOURCE_CLASS_PATH + requestUri + BIODynamicServer.SERVLET_SUFFIX);
//創(chuàng)建Servlet對(duì)象
servletInstance = aClass.newInstance();
//緩存
SERVLET_MAP.put(requestUri, servletInstance);
servletExist = true;
logger.debug("動(dòng)態(tài)資源[{}]存在", requestUri);
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
return false;
}
} else {
//緩存中存在
servletInstance = servlet;
servletExist = true;
logger.debug("動(dòng)態(tài)資源[{}]存在", requestUri);
}
//執(zhí)行業(yè)務(wù)邏輯
try {
Object result = servletInstance.service();
String resp = JSON.toJSONString(result, SerializerFeature.PrettyFormat);
//結(jié)果寫(xiě)入輸出流
BIODynamicServer.writeResponse(outputStream, resp.getBytes(Constants.CHARSET));
} catch (Exception e) {
//響應(yīng)500
staticHandler("500.html", outputStream);
}
return servletExist;
}
-
將結(jié)果寫(xiě)入輸出流
/**
* 寫(xiě)入響應(yīng)
*
* @param outputStream 輸出流
* @param content 內(nèi)容
* @throws IOException
*/
private static void writeResponse(OutputStream outputStream, byte[] content) throws IOException {
//寫(xiě)入基礎(chǔ)響應(yīng)頭
outputStream.write(BASIC_RESPONSE.getBytes(Constants.CHARSET));
//寫(xiě)入服務(wù)器信息
outputStream.write(("Server: futaoServer/1.1" + BIODynamicServer.carriageReturn).getBytes(Constants.CHARSET));
//寫(xiě)入傳輸?shù)恼膬?nèi)容大小
outputStream.write(("content-length: " + content.length + BIODynamicServer.carriageReturn).getBytes(Constants.CHARSET));
//響應(yīng)頭與響應(yīng)體之間需要空一行
outputStream.write(BIODynamicServer.carriageReturn.getBytes(Constants.CHARSET));
//寫(xiě)入響應(yīng)正文
outputStream.write(content);
outputStream.flush();
}
-
測(cè)試 -
index.html
-
靜態(tài)資源

-
動(dòng)態(tài)資源

-
異常情況
# 源代碼
-
源代碼都給你了你還不看看? -
https://github.com/FutaoSmile/learn-IO/tree/master/practice/src/main/java/com/futao/practice/chatroom/bio/v6server
特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒(méi)關(guān)注的小伙伴,可以長(zhǎng)按關(guān)注一下:
長(zhǎng)按訂閱更多精彩▼
如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!