设计一个仅仅返回一段字符串的最小http服务器有助于理解http协议。对于正规的java程序员,这个程序仅仅用于学习,没有实用价值。对于自动化工程师,小型化的http服务器有着重要的工程意义,因为我们通常不怎么喜欢为了一个简单的页面而去安装Tomcat。
要设计这样一个服务器其实非常简单,只需要掌握一点细节。
1、你需要选择一个监听端口,一般网页服务器使用80或8080,但这并不是必须的,可以指定其它未被使用的端口。比如我选择1234端口。用浏览器连接本机1234端口的方法很简单,就是使用这个url:http://localhost:1234
2、我个人比较喜欢使用AIO来创建连接,而新手一般都是使用Server来建立连接。不管是AIO、NIO还是Server最终的效果都是一样的。总之这一步要打开TCP的监听线程。
3、等待连接。注意Server和NIO的连接是阻塞的,AIO是非阻塞的。
4、等待接收。Server字节流和未缓冲的字符流是非阻塞的,缓冲的字符流使用readLine读取时是阻塞的。NIO和AIO接收字节是阻塞的,但不是以流的方式。所以需要使用new String(bytes,encoding)来将字节转换成字符串
5、正确的http服务器是需要分析收到的数据的,但是我们现在不需要。所以直接返回数据。一定要返回正确的报文头。http报文就是一段字符串,以”\r\n”分行,并以第一个空行区分报文头与正文。最小的报文头是这么写的“HTTP/1.0 200 OK\r\n”,加上一个空行和一个正文后变成”HTTP/1.0 200 OK\r\n\r\n正文”
6、这一步很关键,就是要关闭输出。如果使用Server,需要将OutputStream关闭,如果使用NIO或AIO,需要将channel的输出关闭。记住不要直接关闭连接,否则会出异常。虽然网页能正常显示,但是过多的异常可能会导致JVM强制关闭。
下面是我的主程序:
public static void main(String[] args) {
//这个是客户端,你们也可以删除这个线程,用浏览器来测试
StaticThreadTools.execute(() -> {
StaticThreadTools.sleep(1000);
System.out.println(StaticHttpTools.sendPost("http://localhost:1234", "a=a"));
});
//打开AIO的服务器,也可以更换成NIO或者Server
TCPSocketAIOServer server = new TCPSocketAIOServer(1234, 10000, -1);//形参的意思是“端口1234、缓冲区10000字节,阻塞方式读取”。这里没有给出子程序,所以你们不要直接用它来编译。
//打开
server.open();
//http服务器主体
doServer(server);
server.close();
}
private static void doServer(TCPSocketAIOServer server) {
for (;;) {
//等待读取
TCPSocketMessage msg = server.receive();
//跳过空包(这种空包的情况是我的AIO工具类特有的,不要照抄)
if (msg == null) {
break;
}
if (msg.get() == null) {
continue;
}
//建立一个线程来处理消息
StaticThreadTools.execute(() -> {
System.out.println(msg.getRemoteSocketAddress());
//输出报文头
TCPSocketMessage msg2 = new TCPSocketMessage();
final String HEAD = StaticHttpTools.getHttpServerHead(HTTP_CONST.CONTENT_TYPE_HTML, "myServer");
msg2.acceptString(HEAD);
msg.reply(msg2);
//输出正文(这个例子直接使用报文头来当正文)
msg.reply(msg2);
//关闭输出
msg.closeOutput();
});
}
}
关键点:
1、初学者不管收到什么,都返回一个”HTTP/1.0 200 OK\r\n\r\n”就对了。”HTTP/1.0 200 OK\r\n”和一个空行”\r\n”是必须的。然后在这个基础上添加其它的东西。等熟悉了http的流程后,再考虑错误码和属性之类的东西。
2、必须关闭输出流。在这一步flush是无效的,而直接关闭连接会出异常。因为没注意到这点,我浪费了一天的时间浏览器都没响应。