自己动手实现一个 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. ZOJ2481 Unique Ascending Array 2017-04-18 23:08 33人阅读 评论(0) 收藏

    Unique Ascending Array Time Limit: 2 Seconds      Memory Limit: 65536 KB Given an array of integers ...

  2. C++中的乱七八糟问题

    1   在编写的c++程序中,如果是窗口,有时会一闪就消失了,如果不想让其消失,在程序结尾处添加: #include“iostream.h” system("pause"); 分析 ...

  3. uniGUI试用笔记(九)uniGUI执行程序部署有3种形式1

    uniGUI执行程序部署有3种形式 1.ISAPI模式 部署在IIS或Apache,程序编译为Dll形式,没有试,准备后续专门测试一下. 2.标准执行文件模式 将软件编译成一个独立的Exe文件,包括了 ...

  4. [Openwrt 项目开发笔记]:DDNS设置(五)

    [Openwrt项目开发笔记]系列文章传送门:http://www.cnblogs.com/double-win/p/3888399.html 正文: 在上一节中,我主要讲述了如何在Openwrt上安 ...

  5. Microsoft SQL Server 2012 管理 (1): 安装配置SQL Server 重点

    SQL Server 可以在实例,数据库,列,查询分别指定排序规则 /* Module 1 - working with Clollations */ -- 1.1 Obtain the Instan ...

  6. FP-Growth in Spark MLLib

    并行FP-Growth算法思路 上图的单线程形成的FP-Tree. 分布式算法事实上是对FP-Tree进行分割,分而治之 首先,假设我们只关心...|c这个conditional transactio ...

  7. 用注册表禁止windows添加新用户

    运行 regedt32.exe 打开你的注册表,里面有一个目录树:打开其中目录 HKEY_LOCAL_MACHINE再打开其中目录 SAM再打开其中目录 SAM再打开其中目录 Domains再打开其中 ...

  8. 解决EF没有生成字段和表说明

    找了很多资料,终于找到一篇真正能解决ef生成字段说明,注释的文章,收藏不了,于是转载 本文章为转载,原文地址 项目中使用了EF框架,使用的是Database-First方式,因为数据库已经存在,所以采 ...

  9. C# 基础篇

    全篇依据 C#高级编程(第9版) 内容记录: 基础知识C# 5.0 基础 分为15章内容来介绍 核心C# 对象和类型 继承 泛型 数组 运算符和类型强制转换 委托和lambda表达式,事件 字符串和正 ...

  10. Python初学手记----在window系统中安装环境

    官网地址: https://www.python.org/ Win版下载地址:https://www.python.org/downloads/windows/ 安装注意:安装路径推荐修改. path ...