自己动手实现一个 Web Server

项目背景

最近在重温WEB服务器的相关机制和原理,为了方便记忆和理解,就尝试自己用Java写一个简化的WEB SERVER的实现,功能简单,简化了常规服务器的大部分功能和结构封装,但仍然保留从浏览器发送请求到将处理结果返回响应到浏览器的整个流程,现在把相关内容分享出来,供大家参考。

项目环境

IDE : eclipse 4.6.3

JDK : JDK1.8.0_131

Maven : Maven 3.5.2

项目结构

项目比较简单,就用一个普通的Java或Maven工程,引入JDK依赖即可。

工程下只有一个包,共包含六个文件。

WebServer : WEB 服务器主类,里面包含main方法,可直接运行启动服务器。

Request: 请求包装类,包含请求类型,请求URI。

Response:响应包装类,包含输出流,可向浏览器输出响应信息。

RequstParser:请求信息解析类,解析完成后返回一个Request。

ServiceDispacher:服务派发器,这里类似于Srping的DispatcherServlete。(不属于服务器部分)

TestController:模拟控制器返回信息。(不属于服务器部分)

其中ServiceDispacher和TestController,不属于服务器部分,这里为了方便测试,放在一个工程下。

实现流程

实现流程大致如下:

1 创建服务端ServerSocket, 绑定一个 端口号

2 循环监听客户端请求,连接成功后返回一个Socket

3 开启一个新的线程,传入Socket处理当前请求

4 Web Server调用ServiceDispacher进行服务的分发

5 ServiceDispacher根据请求查找并调用相应的控制器

6 控制器方法执行返回结果,并将结果相应到浏览器

代码示例

下面给出完整的代码实现,代码注释已经解释的比较清楚了,在这里就不再多费口舌了,快来源码见。

1 WebServer.java

package com.louis.web.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket; /**
* Web Server
* @author Louis
*/
public class WebServer { /**
* 服务器启动端口
*/
private int port = 8888;
/**
* 服务端Socket
*/
private ServerSocket serverSocket; public WebServer() {
init();
} /**
* 初始化服务端Socket
*/
private void init() {
try {
// 创建服务端Socket
serverSocket = new ServerSocket(port);
System.out.println("服务端已启动,等待客户端连接..");
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 启动服务器,监听并处理客户请求
* @throws IOException
*/
public void start() throws IOException {
while (true) {
// 侦听并接受客户请求
Socket socket = serverSocket.accept();
// 新启线程,处理客户请求
new Thread() {
@Override
public void run() {
service(socket);
}
}.start();
}
} /**
* 处理客户请求
* @param socket
*/
private void service(Socket socket) {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
// 读取请求信息内容
Request request = new RequestParser().parse(inputStream);
Response response = new Response(outputStream);
service(request, response);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭连接
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("接收到客户端连接, " + socket.getInetAddress() + ":" + socket.getPort());
} /**
* 处理客户请求, 把请求交给框架派遣服务,类似Spring的DispatcherServlet
* @param request
* @param response
*/
private void service(Request request, Response response) {
ServiceDispatcher serviceDispatcher = new ServiceDispatcher();
serviceDispatcher.dispatcher(request, response);
} public static void main(String[] args) {
try {
new WebServer().start();
} catch (IOException e) {
e.printStackTrace();
}
}
}

2 Request.java

package com.louis.web.server;

/**
* Request
* @author Louis
*/
public class Request {
/**
* 请求方式: GET\POST\DELETE..
*/
private String type;
/**
* 请求URI
*/
private String uri; public String getType() {
return type;
} public void setType(String type) {
this.type = type;
} public String getUri() {
return uri;
} public void setUri(String uri) {
this.uri = uri;
} }

3 Response.java

package com.louis.web.server;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream; /**
* Response
*
* @author Louis
*/
public class Response {
private OutputStream output; public Response(OutputStream output) {
this.output = output;
} /**
* 输出文本信息
* @param text
* @throws IOException
*/
public void writeText(String text) {
FileInputStream fis = null;
try {
output.write("HTTP/1.1 200 OK\n".getBytes());
output.write("Content-Type: text/html; charset=UTF-8\n\n".getBytes());
output.write(text.getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

4 RequestParser.java

package com.louis.web.server;
import java.io.InputStream; /**
* Request Parser
* @author Louis
*/
public class RequestParser {
private final static int BUFFER_SIZE = 1024; /**
* 解析请求
* @param inputStream
* @return Request
*/
public Request parse(InputStream inputStream) {
Request request = new Request();
// 读取请求信息
String requestMessage = readRequestMessage(inputStream);
// 解析请求方式
String type = parseType(requestMessage);
request.setType(type);
// 解析请求类型
String uri = parseUri(requestMessage);
request.setUri(uri);
return request;
} /**
* 读取请求信息
* @param input
* @return
*/
private String readRequestMessage(InputStream input) {
StringBuffer requestMessage = new StringBuffer();
int readLength = 0;
byte[] buffer = new byte[BUFFER_SIZE];
try {
readLength = input.read(buffer);
} catch (Exception e) {
e.printStackTrace();
readLength = -1;
}
for(int i = 0; i < readLength; i++) {
requestMessage.append((char) buffer[i]);
}
return requestMessage.toString();
} /**
* 解析请求方式
* @param requestString
* @return
*/
private String parseType(String requestString) {
int index = 0;
index = requestString.indexOf(' ');
if (index != -1) {
return requestString.substring(0, index);
}
return null;
} /**
* 解析请求类型
* @param requestString
* @return
*/
private String parseUri(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
} }

5 ServiceDispatcher.java

package com.louis.web.server;

/**
* 根据请求类型和URI找到对应的控制器,将请求交给控制器处理
*
* @author Louis
*/
public class ServiceDispatcher { /**
* 转发处理请求
* @param request
* @param response
*/
public void dispatcher(Request request, Response response) {
execController(request, response);
} /**
* 根据请求类型及URI等请求信息,找到并执行对应的控制器方法后返回
* 此处直接返回一个控制器,模拟查找和执行控制器方法的过程
* @param request
* @param response
* @return
*/
private void execController(Request request, Response response) {
String text = getControllerResult(request, response);
StringBuilder sb = new StringBuilder();
sb.append("请求类型: " + request.getType());
sb.append("<br/>请求URI: " + request.getUri());
sb.append("<br/>返回结果: " + text);
// 输出控制器返回结果
response.writeText(sb.toString());
} /**
* 模拟查找和执行控制器方法并返回结果
* @param request
* @param response
* @return
*/
private String getControllerResult(Request request, Response response) {
String text = "";
String uri = request.getUri();
String [] uriArray = uri.split("\\/");
if(uriArray.length != 3) {
text = "请求路径没有找到相关匹配服务. ";
} else if("test".equalsIgnoreCase(uriArray[1])) {
TestController testController = new TestController();
if("test1".equalsIgnoreCase(uriArray[2])) {
text = testController.test1();
} else if("test2".equalsIgnoreCase(uriArray[2])) {
text = testController.test2();
} else {
text = "请求路径没有找到相关匹配服务. ";
}
} else {
text = "请求路径没有找到相关匹配服务. ";
}
return text;
} }

6 TestController.java

package com.louis.web.server;

public class TestController {

    public String test1() {
return "TestController.test1() 调用成功";
} public String test2() {
return "TestController.test2() 调用成功";
}
}

启动测试

直接运行WebServer的main方法即可,控制台输出“服务端已启动,等待客户端连接..”, 启动成功。

启动完成之后,浏览器访问分别访问不同路径,查看响应结果。

如下图所示:

http://localhost:8888/test/test1

http://localhost:8888/test/test2

http://localhost:8888/test/test3

http://localhost:8888/test/mack

我们看到,当调用 /test/test1 和 /test/test2 的时候,控制器 TestController 的 test1 和 test2 方法相应被调用成功。而输入 test3 获取其他不存在的服务的时候,将会得到“请求路径没有找到相关匹配服务”的响应。

源码下载

码云:https://gitee.com/liuge1988/web-server


作者:朝雨忆轻尘
出处:https://www.cnblogs.com/xifengxiaoma/
版权所有,欢迎转载,转载请注明原文作者及出处。

自己动手实现一个WEB服务器的更多相关文章

  1. C#中自己动手创建一个Web Server(非Socket实现)

    目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 ...

  2. 用java写一个web服务器

    一.超文本传输协议 Web服务器和浏览器通过HTTP协议在Internet上发送和接收消息.HTTP协议是一种请求-应答式的协议——客户端发送一个请求,服务器返回该请求的应答.HTTP协议使用可靠的T ...

  3. 树莓派变成一个Web服务器: nginx + php + sqlite

    将树莓派变成一个Web服务器,通过访问网页,就可以控制树莓派,比如:查看摄像头\开灯等等. 一想到Linux Web服务器,我们首先想到的是,Apache + MySql + Php. 树莓派可以安装 ...

  4. 用C写一个web服务器(二) I/O多路复用之epoll

    .container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .conta ...

  5. 【重点突破】——使用Express创建一个web服务器

    一.引言 在自学node.js的过程中有一个非常重要的框架,那就是Express.它是一个基于NodeJs http模块而编写的高层模块,弥补http模块的繁琐和不方便,能够快速开发http服务器.这 ...

  6. C++实现一个web服务器, 弱智版服务器

    监听本地的8888端口, 当在浏览器中访问这个地址的时候, 返回一堆HTML数据, 这种方式返回的数据不稳定,不同浏览器解析不同, 因为我们没有定义返回文件类型: #include <stdli ...

  7. 树莓派(raspberry pi)学习11: 将树莓派变成一个Web服务器(转)

    将树莓派变成一个Web服务器,通过访问网页,就可以控制树莓派,比如:查看摄像头\开灯等等. 一想到Linux Web服务器,我们首先想到的是,Apache + MySql + Php. 树莓派可以安装 ...

  8. 十七、创建一个 WEB 服务器(一)

    1.Node.js 创建的第一个应用 var http=require("http") http.createServer(function (req,res) { res.wri ...

  9. 使用node.js 文档里的方法写一个web服务器

    刚刚看了node.js文档里的一个小例子,就是用 node.js 写一个web服务器的小例子 上代码 (*^▽^*) //helloworld.js// 使用node.js写一个服务器 const h ...

随机推荐

  1. (网络流 最大流 Dinic || SAP)Control -- hdu --4289

    链接: http://acm.hdu.edu.cn/showproblem.php?pid=4289 http://acm.hust.edu.cn/vjudge/contest/view.action ...

  2. spring案列——annotation配置

    一.需要的jar包 spring.jar commons-loggin.jar commons-loggin.jar commons-annotation.jar 二.项目结构 三.entity pa ...

  3. 百分之 95% 的程序员不知道 Trending 是什么。

    前言如果学习到的知识不成体系,那么遇到问题时就会非常难解决.常有人问你从哪里了解新技术怎么判断其发展趋势的,除了关注 Hacker News 以及庞大的 Awesome 还有没有其它方式?有啊当然是每 ...

  4. NETSDK1061错误解决

    NETSDK1061错误解决 在vs生成和运行都正常,发布的时候报错 .netcore控制台项目引用另一个类库 错误信息 NETSDK1061: 项目是使用 Microsoft.NETCore.App ...

  5. VisualStudio、NETFramework及C#版本关系

    1.Visual Studio..NET Framework 及C#版本搭载关系介绍 Visual Studio版本 .NET Framework版本 C#版本 增加功能 Visual Studio ...

  6. 数据分析融入至BI工具的新思路

    欢迎访问网易云社区,了解更多网易技术产品运营经验. 很认同一种说法:BI对企业的作用不是BI本身,而是数据本身.那么BI的作用和意义是干什么的呢-是让有意义的数据自己说出自己意义的工具. BI发展至今 ...

  7. 网易云社区有奖问答活动第二期——技术领导力、深入分布式、PHP圣经、Linux运维、Unity……三月热点图书等你拿!

    网易云社区第二期有奖问答活动开始了!(第一期活动已结束:人工智能图书大抽奖!) 欢迎积极参与网易云社区,讨论问题,交流心得.我们本期准备了一批技术领域热点图书,送给参与社区的朋友们,将以抽奖的形式送出 ...

  8. PageAdmin CMS网站建设教程:如何实现信息的定时发布

    PageAdmin Cms发布文章时候有一个上线时间设置和下线时间设置,网站编辑人员可以利用这个功能来实现定时发布,在信息发布界面,如下图: 设置后就会自动加入定时任务中,注意这个功能需要再系统设置& ...

  9. BZOJ 1002--[FJOI2007]轮状病毒(高精度)

    1002: [FJOI2007]轮状病毒 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 6858  Solved: 3745[Submit][Statu ...

  10. AVFoundation - 拍照(Simple)

    1:基础 /* 1:获取可用输入设备 AVCaptureDevice 2:设置输入设备: [AVCaptureDeviceInput deviceInputWithDevice:self.captur ...