每日一例 | 昨天手写服务器问题搞定了……后面继续搞事情……
共 16900字,需浏览 34分钟
·
2021-06-02 02:36
问题搞定了
昨天的问题,已经找到原因了,是因为请求参数里有空字符串的情况,就是这些空字符串,导致inputStream
被阻塞,所以最终无法响应给浏览器端。
昨天的doService
方法是这样写的:
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("请求参数:%s", requestStr));
String[] split = requestStr.split("\r\n");
System.out.println("end");
response.write("hello syskeCat");
} catch (Exception e) {
e.printStackTrace();
}
}
简单调整下,就可以了
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("请求参数:%s", requestStr));
String[] split = requestStr.split("\r\n");
System.out.println("end");
response.write("hello syskeCat");
} catch (Exception e) {
e.printStackTrace();
}
}
我把不一样的地方单独提出来:
这是原来的写法:
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;
}
}
其实,写这段代码就是为了拿到请求头和请求参数:
{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
循环也行,但是也就拿不到完整的请求信息了。而且经过我的实验,如果把空字符判断那块拿掉,就又会出现昨天的情况了。
项目已建好
昨天立的flag
,今天已经把仓库建好了,写了一些简单的代码,以后就是慢慢添砖添瓦了。我们先来看看,现在是什么进展。
项目结构
创建了一个maven
项目,目前只实现了简单的Request
和Response
,可以实现简单的请求,但是还不能根据不同的请求返回对应的结果,这是下一步的工作。
关于这个项目,初步的计划是,后面会逐步实现Springboot
的一些简单功能,最后希望能通过这个项目,能够更深入地了解springboot
,了解web
服务器。目前是基于传统的socket
实现的,后面想以NIO
的方式再写一遍,大概率会用Netty
。
服务器入口
这里启用了一个线程池来处理客户端的请求
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();
}
/**
* 启动服务器
* @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("服务器后端异常");
}
}
}
请求处理线程
这里的核心在run
方法,也就是在这里处理客户端的请求,后期要着重实现doDispatcher()
方法,实现请求的分发和响应。在SyskeRequestHandler
被实例化的时候,会根据客户端socket
构建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("系统错误:", e);
}
}
/**
* 请求分发处理
* @throws Exception
*/
public void doDispatcher() throws Exception{
logger.info("请求头信息:{}", syskeRequest.getHeader());
logger.info("请求信息:{}", syskeRequest.getRequestAttributeMap());
syskeResponse.write(String.format("hello syskeCat, dateTime:%d", System.currentTimeMillis()));
socket.close();
}
}
请求封装
除了获取inputStream
,还有获取请求参数和请求头的封装。后面还要继续完善请求头这一块,实现获取请求地址和具体参数的需求。
public class SyskeRequest implements Request {
/**
* 输入流
*/
private InputStream inputStream;
/**
* 请求参数
*/
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;
}
/**
* 初始化请求
*
* @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;
}
}
响应封装
现在的响应信息太单一了,不论前端发送啥请求,后端始终响应的是html
,这块也有再优化。下一步就是要实现根据请求头,响应对应的数据信息。
public class SyskeResponse implements Response {
private OutputStream outputStream;
public SyskeResponse(OutputStream outputStream) {
this.outputStream = outputStream;
}
@Override
public OutputStream getOutputStream() {
return outputStream;
}
//将文本转换为字节流
public void write(String content) throws IOException {
StringBuffer httpResponse = new StringBuffer();
// 按照HTTP响应报文的格式写入
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>");
// 将文本转为字节流
outputStream.write(httpResponse.toString().getBytes());
outputStream.flush();
outputStream.close();
}
}
总结
flag
立好了,项目也建起来,后面就是实现自己的想法了。说实话,我现在还是挺期待的,感觉这是个很有意思的事情。可能看文章的小伙伴不一定能get
到任何点,但对我来说真的是颠覆性的,昨天我也说了,今天依然有这样的感觉。以前,顶多是写写前端页面,写写后端接口,但是这个请求如何从前端到后端的,就是特别谜,也从来没想明白,但是昨天一下就醍醐灌顶了,感觉这一道大门终于被打通了,这感觉有一种发自心底的愉悦和兴奋,我甚至有种想马上把其他功能需求都实现的冲动。
下面是项目的开源仓库,有兴趣的小伙伴可以去看看,如果有想法的小伙伴,我真心推荐你自己动个手,自己写一下,真的感觉不错:
https://github.com/Syske/syske-boot
- END -