java实现web服务器
我写过一个玩具web服务器,实现用Java原生Socket,实现没碰任何框架。实现总共三百行代码,实现能跑静态页面,实现能处理GET请求。实现那玩意儿让我突然明白,实现Tomcat那类庞然大物,实现骨子里也就是实现这么回事。
核心就一行:ServerSocket serverSocket = new ServerSocket(8080)。实现然后死循环里accept(),实现每个连接丢给线程池。实现别笑,实现真就这么简单。实现浏览器发来的实现是纯文本HTTP协议,你读出来解析请求行,比如GET /index.html HTTP/1.1,再按路径从磁盘取文件,拼上响应头写回去。
响应头得手动拼,这地方坑多。第一行状态码,接着Content-Type、Content-Length,最后空一行,再塞文件字节流。漏掉空行浏览器直接不认,我当年卡了一下午,最后用十六进制工具看字节流才发现问题。那种抓狂感,比看十遍理论都管用。
线程模型也值得琢磨。最开始我每个请求new一个线程,压测时一百个并发就把机器搞垮了。后来换成固定线程池,瞬间稳了。这里有个观察:线程创建开销远比你想象的大,尤其在频繁短连接场景下。你要是真要做生产级服务器,还得考虑NIO或者Netty,但理解阻塞IO的瓶颈在哪,比直接上异步框架更有价值。
处理静态文件有个细节:文件名得做安全校验。直接拼接路径的话,../../etc/passwd这种请求能把服务器文件泄露出去。我用Paths.get(baseDir, requestPath).normalize(),再检查结果是否以baseDir开头,才算堵住漏洞。这个案例说明,写服务器代码,安全思维得从第一行就绷着。
动态内容怎么搞?简单做法是留个接口,比如路径以/api/开头就调用你注册的handler。Java 8的lambda表达式这时候特别好用,handlerMap.put("/api/time", (req, res) -> res.write("now"))。我甚至用这个思路做过一个极简REST框架,支持路径参数匹配,核心逻辑不超过五十行。
性能方面别抱幻想。纯Java IO的服务器,单机撑几千并发就到头了。但反过来想,大部分内部工具、个人项目,这个量级绰绰有余。我拿自己的服务器跑过一个博客系统,每天几百访问量,稳如老狗。省掉Spring Boot那几十兆依赖,启动速度零点几秒,部署就是拷个jar包。
有个体验值得分享:用Java写web服务器,异常处理比业务逻辑还占代码量。客户端突然断开、请求头超长、文件权限不足,每种情况都得兜住。我的做法是顶层catch Exception,返回500页面,再记录日志。别追求完美恢复,先保证进程不死,这是服务器代码的生存哲学。
你要是真想动手试试,建议按这个顺序来:先跑通单线程,再改线程池,然后加静态资源,最后扩展动态接口。每一步都能跑,每步都有成就感。别一上来就设计什么插件体系、过滤器链,那些都是后话。我见过太多人死在“完美架构”的起步阶段,代码写了两千行,还没成功响应过一个HTTP请求。
写web服务器最妙的地方在于,它逼你把网络协议、并发模型、文件IO、字符串解析这些基础概念全部串起来。Java的类库帮你挡了大部分底层细节,但关键的抽象还保留着。搞懂这一层,以后看任何Java web框架的源码,都跟看熟人似的。
最后说个玄学:我那个三百行的服务器,运行了三年没崩过。后来为了加HTTPS支持,换了Netty,反而折腾出几次内存泄漏。有时候简单到极致,也是一种可靠。