每日一例 | 昨天手寫(xiě)服務(wù)器問(wèn)題搞定了……后面繼續(xù)搞事情……

問(wèn)題搞定了
昨天的問(wèn)題,已經(jīng)找到原因了,是因?yàn)檎?qǐng)求參數(shù)里有空字符串的情況,就是這些空字符串,導(dǎo)致inputStream被阻塞,所以最終無(wú)法響應(yīng)給瀏覽器端。
昨天的doService方法是這樣寫(xiě)的:
public void doService(SyskeRequest request, SyskeResponse response) throws Exception {
try {
byte[] bytes = new byte[1024];
int read;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while ((read = request.getInputStream().read(bytes)) != -1) {
byteArrayOutputStream.write(bytes);
}
byte[] toByteArray = byteArrayOutputStream.toByteArray();
String requestStr = new String(toByteArray);
System.out.println(String.format("請(qǐng)求參數(shù):%s", requestStr));
String[] split = requestStr.split("\r\n");
System.out.println("end");
response.write("hello syskeCat");
} catch (Exception e) {
e.printStackTrace();
}
}
簡(jiǎn)單調(diào)整下,就可以了
public static void doService(SyskeRequest request, SyskeResponse response) {
try {
String readLine;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()));
while ((readLine = bufferedReader.readLine()) != null) {
if(readLine.length() == 0){
break;
}
byteArrayOutputStream.write(bytes);
}
byte[] toByteArray = byteArrayOutputStream.toByteArray();
String requestStr = new String(toByteArray);
System.out.println(String.format("請(qǐng)求參數(shù):%s", requestStr));
String[] split = requestStr.split("\r\n");
System.out.println("end");
response.write("hello syskeCat");
} catch (Exception e) {
e.printStackTrace();
}
}
我把不一樣的地方單獨(dú)提出來(lái):
這是原來(lái)的寫(xiě)法:
while ((read = request.getInputStream().read(bytes)) != -1) {
byteArrayOutputStream.write(bytes);
}
修改之后:
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()));
while ((readLine = bufferedReader.readLine()) != null) {
if(readLine.length() == 0){
break;
}
}
其實(shí),寫(xiě)這段代碼就是為了拿到請(qǐng)求頭和請(qǐng)求參數(shù):
{Cookie= NMTID=00O3GJZgJXpkp8vI0cpmNRliUnGvYkAAAF4sPsi2w, Accept= text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9, Connection= keep-alive, User-Agent= Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36, Sec-Fetch-Site= cross-site, Sec-Fetch-Dest= document, Host= localhost:8080, Accept-Encoding= gzip, deflate, br, Sec-Fetch-Mode= navigate, sec-ch-ua= " Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90", sec-ch-ua-mobile= ?0, Cache-Control= max-age=0, Upgrade-Insecure-Requests= 1, Sec-Fetch-User= ?1, Accept-Language= zh-CN,zh;q=0.9}
直接刪除掉while循環(huán)也行,但是也就拿不到完整的請(qǐng)求信息了。而且經(jīng)過(guò)我的實(shí)驗(yàn),如果把空字符判斷那塊拿掉,就又會(huì)出現(xiàn)昨天的情況了。
項(xiàng)目已建好
昨天立的flag,今天已經(jīng)把倉(cāng)庫(kù)建好了,寫(xiě)了一些簡(jiǎn)單的代碼,以后就是慢慢添磚添瓦了。我們先來(lái)看看,現(xiàn)在是什么進(jìn)展。
項(xiàng)目結(jié)構(gòu)
創(chuàng)建了一個(gè)maven項(xiàng)目,目前只實(shí)現(xiàn)了簡(jiǎn)單的Request和Response,可以實(shí)現(xiàn)簡(jiǎn)單的請(qǐng)求,但是還不能根據(jù)不同的請(qǐng)求返回對(duì)應(yīng)的結(jié)果,這是下一步的工作。
關(guān)于這個(gè)項(xiàng)目,初步的計(jì)劃是,后面會(huì)逐步實(shí)現(xiàn)Springboot的一些簡(jiǎn)單功能,最后希望能通過(guò)這個(gè)項(xiàng)目,能夠更深入地了解springboot,了解web服務(wù)器。目前是基于傳統(tǒng)的socket實(shí)現(xiàn)的,后面想以NIO的方式再寫(xiě)一遍,大概率會(huì)用Netty。

服務(wù)器入口
這里啟用了一個(gè)線(xiàn)程池來(lái)處理客戶(hù)端的請(qǐng)求
public class SyskeBootServerApplication {
private static final Logger logger = LoggerFactory.getLogger(SyskeBootServerApplication.class);
private static final int SERVER_PORT = 8080;
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
public static void main(String[] args) {
start();
}
/**
* 啟動(dòng)服務(wù)器
* @throws Exception
*/
public static void start() {
try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT) ) {
Socket accept = null;
logger.info("SyskeCatServer is starting……, port: {}", SERVER_PORT);
while ((accept = serverSocket.accept()) != null){
threadPoolExecutor.execute(new SyskeRequestHandler(accept));
}
} catch (Exception e) {
logger.error("服務(wù)器后端異常");
}
}
}
請(qǐng)求處理線(xiàn)程
這里的核心在run方法,也就是在這里處理客戶(hù)端的請(qǐng)求,后期要著重實(shí)現(xiàn)doDispatcher()方法,實(shí)現(xiàn)請(qǐng)求的分發(fā)和響應(yīng)。在SyskeRequestHandler被實(shí)例化的時(shí)候,會(huì)根據(jù)客戶(hù)端socket構(gòu)建requst和response。
public class SyskeRequestHandler implements Runnable {
private final Logger logger = LoggerFactory.getLogger(SyskeRequestHandler.class);
private Socket socket;
private SyskeRequest syskeRequest;
private SyskeResponse syskeResponse;
public SyskeRequestHandler(Socket socket) throws IOException{
this.socket = socket;
init();
}
private void init() throws IOException{
this.syskeRequest = new SyskeRequest(socket.getInputStream());
this.syskeResponse = new SyskeResponse(socket.getOutputStream());
}
@Override
public void run() {
try {
doDispatcher();
} catch (Exception e) {
logger.error("系統(tǒng)錯(cuò)誤:", e);
}
}
/**
* 請(qǐng)求分發(fā)處理
* @throws Exception
*/
public void doDispatcher() throws Exception{
logger.info("請(qǐng)求頭信息:{}", syskeRequest.getHeader());
logger.info("請(qǐng)求信息:{}", syskeRequest.getRequestAttributeMap());
syskeResponse.write(String.format("hello syskeCat, dateTime:%d", System.currentTimeMillis()));
socket.close();
}
}
請(qǐng)求封裝
除了獲取inputStream,還有獲取請(qǐng)求參數(shù)和請(qǐng)求頭的封裝。后面還要繼續(xù)完善請(qǐng)求頭這一塊,實(shí)現(xiàn)獲取請(qǐng)求地址和具體參數(shù)的需求。
public class SyskeRequest implements Request {
/**
* 輸入流
*/
private InputStream inputStream;
/**
* 請(qǐng)求參數(shù)
*/
private Map<String, String> requestAttributeMap;
private String header;
public SyskeRequest(InputStream inputStream) throws IOException{
this.inputStream = inputStream;
initRequest();
}
@Override
public InputStream getInputStream() {
return inputStream;
}
@Override
public Map<String, String> getRequestAttributeMap() throws IOException{
if (requestAttributeMap != null) {
return requestAttributeMap;
}
initRequest();
return requestAttributeMap;
}
/**
* 初始化請(qǐng)求
*
* @throws IOException
*/
private void initRequest() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
header = bufferedReader.readLine();
Map<String, String> attributeMap = Maps.newHashMap();
String readLine = null;
while ((readLine = bufferedReader.readLine()) != null) {
if(readLine.length()==0) {
break;
}
if (readLine.contains(":")) {
String[] split = readLine.split(":", 2);
attributeMap.put(split[0], split[1]);
}
}
requestAttributeMap = attributeMap;
}
public String getHeader() {
return header;
}
}
響應(yīng)封裝
現(xiàn)在的響應(yīng)信息太單一了,不論前端發(fā)送啥請(qǐng)求,后端始終響應(yīng)的是html,這塊也有再優(yōu)化。下一步就是要實(shí)現(xiàn)根據(jù)請(qǐng)求頭,響應(yīng)對(duì)應(yīng)的數(shù)據(jù)信息。
public class SyskeResponse implements Response {
private OutputStream outputStream;
public SyskeResponse(OutputStream outputStream) {
this.outputStream = outputStream;
}
@Override
public OutputStream getOutputStream() {
return outputStream;
}
//將文本轉(zhuǎn)換為字節(jié)流
public void write(String content) throws IOException {
StringBuffer httpResponse = new StringBuffer();
// 按照HTTP響應(yīng)報(bào)文的格式寫(xiě)入
httpResponse.append("HTTP/1.1 200 OK\n").append("Content-Type:text/html\n").append("\r\n")
.append("<html><head><link rel=\"icon\" href=\"data:;base64,=\"></head><body>").append(content)
.append("</body></html>");
// 將文本轉(zhuǎn)為字節(jié)流
outputStream.write(httpResponse.toString().getBytes());
outputStream.flush();
outputStream.close();
}
}
總結(jié)
flag立好了,項(xiàng)目也建起來(lái),后面就是實(shí)現(xiàn)自己的想法了。說(shuō)實(shí)話(huà),我現(xiàn)在還是挺期待的,感覺(jué)這是個(gè)很有意思的事情。可能看文章的小伙伴不一定能get到任何點(diǎn),但對(duì)我來(lái)說(shuō)真的是顛覆性的,昨天我也說(shuō)了,今天依然有這樣的感覺(jué)。以前,頂多是寫(xiě)寫(xiě)前端頁(yè)面,寫(xiě)寫(xiě)后端接口,但是這個(gè)請(qǐng)求如何從前端到后端的,就是特別謎,也從來(lái)沒(méi)想明白,但是昨天一下就醍醐灌頂了,感覺(jué)這一道大門(mén)終于被打通了,這感覺(jué)有一種發(fā)自心底的愉悅和興奮,我甚至有種想馬上把其他功能需求都實(shí)現(xiàn)的沖動(dòng)。
下面是項(xiàng)目的開(kāi)源倉(cāng)庫(kù),有興趣的小伙伴可以去看看,如果有想法的小伙伴,我真心推薦你自己動(dòng)個(gè)手,自己寫(xiě)一下,真的感覺(jué)不錯(cuò):
https://github.com/Syske/syske-boot

