打造一款属于自己的web服务器——从简单开始

距离开篇已经过了很久,期间完善了一下之前的版本,目前已经能够完好运行,基本上该有的功能都有了,此外将原来的测试程序改为示例项目,新项目只需按照示例项目结构实现controller和view即可,详情见:
easy-httpserverdemo-httpsrever
    这次我们将首先来实现一个简单版本,该版本只包括一些基本的功能,后续版本也将在此基础上一步步改进。

一、准备工作

俗话说的好,工欲善其事,必先利其器。我们在开始开发之前应做好如下准备(真的很简单):

  • java开发环境,IDE依据个人爱好,JDK1.6+(1.6之后才自带httpserver)

  • maven环境,项目使用maven构建

  • git,如果你想clone我的代码做参考的话,当然github也支持直接下载zip包

、功能和结构设计

我们动手写代码之前应该先确定好项目的功能,并设计好项目的结构,虽然我们目前需要实现的很简单,但是还是应该简单的进行一番设计。项目计划的功能如下:

  • 基本的http请求接收和响应

  • 可以分别处理动态和静态资源请求,对于静态请求直接返回对应资源,对于动态请求处理后返回

  • 简单的模板处理,通过替代符替换的方法实现模板数据渲染

  • 简单的log支持,不是必须,但是却很有用

看起来并没有多少功能,但是其实仅仅这几个功能我们就能完成一个小型的动态网站了。在这里需要提一点,如果你还一点都不了解web服务器的工作流程,可以先看
这篇博客了解一下。这里我们先看一些本次实现的服务器的工作流程图(用在线的
gliffy画的,挺不错):

现在功能方面已经清晰了,那么我们据此来分析一下项目结构的设计,对于http的请求和响应处理我们目前直接使用jdk自带的httpserver处理(httpserver使用);我们需要实现一个比较核心的模块实现各部分之间的衔接,根据httpserver我们可以实现一个EHHttpHandler来处理,其实现了HttpHandler接口,主要功能是接收http请求,判断类型,解析参数,调用业务处理conroller,调用视图处理Viewhandler,最后响应http请求。此外,为了处理业务和视图渲染我们需实现controller和view相关的类。具体结构代码见后边。

三、实现代码


1、新建并配置项目

首先我们需要新建一个maven项目,首先建立如下结构:

其中主要几个文件夹和类的功能如下:

  • Constants.java存放系统常量,目前主要存放一些路径,如静态文件夹路径等;

  • EHServer是入口类,在这里我们初始化配置,并启动server。

  • EHHttpHandler功能前边已经说过,是项目最核心的类;

  • ResultInfo是一个实体类,主要用来传输Controller处理后的结果;

  • Controller是一个空接口,主要考虑后期拓展;IndexController是业务处理类,其可调用server进行业务处理,并返回结果;

  • ViewHandler是视图处理,根据controller返回的路径和参数集合,找到对应模板页,并替换参数

  • src/main/view文件夹主要存放静态资源(static下)和模板(page下,后缀为.page)

好了,文件结构建好了,我们接下来配置maven依赖,由于主要使用的是jdk自带的包,因此依赖只需要junit和common-log模块,pom.xml如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>learn-1</groupId>
<artifactId>learn-1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>learn-1</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<build>
<finalName>easy-httpserver</finalName>
<resources>
<resource>
<directory>${basedir}/src/main/view</directory>
</resource>
</resources>
</build>
</build>
</project>

pom.xml

2、实现主服务类EHServer


EHServer用来加载配置,初始化基本信息和启动Server,其代码如下:

/**
* 主服务类
* @author guojing
* @date 2014-3-3
*/
public class EHServer {
private final Log log = LogFactory.getLog(EHServer.class);

/**
* 初始化信息,并启动server
*/
public void startServer() throws IOException {
log.info("Starting EHServer......");

//设置路径

Constants.CLASS_PATH = this.getClass().getResource("/").getPath();
Constants.VIEW_BASE_PATH = "page";
Constants.STATIC_RESOURCE_PATH = "static";

//设置端口号
int port = 8899;

// 启动服务器
HttpServerProvider provider = HttpServerProvider.provider();
HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(port), 100);
httpserver.createContext("/", new EHHttpHandler());
httpserver.setExecutor(null);
httpserver.start();
log.info("EHServer is started, listening at 8899.");
}

/**
* 项目main
*/
public static void main(String[] args) throws IOException {
new EHServer().startServer();
}
}

可以看到上边代码使用了httpserver,那么接收的请求又是如何处理的呢?我们可以看到httpserver绑定了EHHttpserver类,而具体的处理就是在该类内完成的,下边我们就来看看该类的实现。

3、实现EHHttpserver、controller和viewHandler

EHHttpserver实现了HttpHandler,而HttpHandler是httpserver包提供的,实现其内的handle()方法后,在接收到请求后,httpserver将调用该方法进行处理。我们将在该方法内判断请求类型并进行相应处理,handle实现代码如下:

    public void handle(HttpExchange httpExchange) throws IOException {
try {
String path = httpExchange.getRequestURI().getPath();
log.info("Receive a request,Request path:" + path);

// 根据后缀判断是否是静态资源
String suffix = path
.substring(path.lastIndexOf("."), path.length());
if (Constants.STATIC_SUFFIXS.contains(suffix)) {
byte[] bytes = IOUtil.readFileByBytes(Constants.CLASS_PATH
+ "static" + path);
responseStaticToClient(httpExchange, 200, bytes);
return;
}

// 调用对应处理程序controller
ResultInfo resultInfo = invokController(httpExchange);

// 返回404
if (resultInfo == null || StringUtil.isEmpty(resultInfo.getView())) {
responseToClient(httpExchange, 200, "<h1>页面不存在<h1>");
return;
}

// 解析对应view并返回
String content = invokViewHandler(resultInfo);
if (content == null) {
content = "";
}
responseToClient(httpExchange, 200, content);
return;

} catch (Exception e) {
httpExchange.close();
log.error("响应请求失败:", e);
}
}

可以看到首先根据url后缀判断请求资源是否属于静态资源,如果是的话,则读取对应资源并调用responseStaticToClient返回,如果不是则调用invokController进行业务处理,而invokController内部十分简单,仅实例化一个IndexController(本次示例controller,直接写死,以后将使用反射动态映射),调用其process方法。IndexController代码如下:

?





1

2

3

4

5

6

7

8

9

10

11

12

13

/**

 * 主页对应的contoller

 * @author guojing

 */

public class IndexController implements
Controller{

    

    public
ResultInfo process(Map<String, Object> map){

        ResultInfo result =new
ResultInfo();

        result.setView("index");

        result.setResultMap(map);

        return
result;

    }

}

在controller中示例了一个ResultInfo对象,并设置view为index(模板路径为page/index.page),并设置将请求参数直接赋值。而EHHttpserver在调用controller后,将ResultInfo传递给invokViewHandler处理。invokViewHandler和invokeController一样,只是一个适配方法,其内部调用ViewHandler进行处理,ViewHandler将找到对应模板,并将其中替换符(这里定义为${XXXXX})替换为对应参数的值,其代码如下:

/**
* 处理页面信息
* @author guojing
* @date 2014-3-3
*/
public class ViewHandler {

/**
* 处理View模板,只提供建单变量(格式${XXX})替换,已废弃
* @return
*/
public String processView(ResultInfo resultInfo) {
// 获取路径
String path = analysisViewPath(resultInfo.getView());
String content = "";
if (IOUtil.isExist(path)) {
content = IOUtil.readFile(path);
}

if (StringUtil.isEmpty(content)) {
return "";
}

// 替换模板中的变量,替换符格式:${XXX}
for (String key : resultInfo.getResultMap().keySet()) {
String temp = "";
if (null != resultInfo.getResultMap().get(key)) {
temp = resultInfo.getResultMap().get(key).toString();
}
content = content.replaceAll("\\$\\{" + key + "\\}", temp);
}

return content;
}

/**
* 解析路径(根据Controller返回ResultInfo的view),已废弃
* @param viewPath
* @return
*/
private String analysisViewPath(String viewPath) {
String path = Constants.CLASS_PATH
+ (Constants.VIEW_BASE_PATH == null ? "/" : Constants.VIEW_BASE_PATH+"/")
+ viewPath + ".page";
return path;
}
}

在ViewHandler处理完后,就可以返回数据了,因为处理不同,这里把动态请求和静态请求分开处理,代码如下;

/**
* 响应请求
*
* @param httpExchange
* 请求-响应的封装
* @param code
* 返回状态码
* @param msg
* 返回信息
* @throws IOException
*/
private void responseToClient(HttpExchange httpExchange, Integer code,
String msg) throws IOException {

switch (code) {
case 200: { // 成功
byte[] bytes = msg.getBytes();
httpExchange.sendResponseHeaders(code, bytes.length);
OutputStream out = httpExchange.getResponseBody();
out.write(bytes);
out.flush();
httpExchange.close();
}
break;
case 302: { // 跳转
Headers headers = httpExchange.getResponseHeaders();
headers.add("Location", msg);
httpExchange.sendResponseHeaders(code, 0);
httpExchange.close();
}
break;
case 404: { // 错误
byte[] bytes = "".getBytes();
httpExchange.sendResponseHeaders(code, bytes.length);
OutputStream out = httpExchange.getResponseBody();
out.write(bytes);
out.flush();
httpExchange.close();
}
break;
default:
break;
}
}

/**
* 响应请求,返回静态资源
*
* @param httpExchange
* @param code
* @param bytes
* @throws IOException
*/
private void responseStaticToClient(HttpExchange httpExchange,
Integer code, byte[] bytes) throws IOException {
httpExchange.sendResponseHeaders(code, bytes.length);
OutputStream out = httpExchange.getResponseBody();
out.write(bytes);
out.flush();
httpExchange.close();
}

四、测试项目


至此,我们已经完成了之前预期的功能,现在我们来测试一下到底能否运行。我们在src/main/view/下本别建立如下文件:

其中index.page是动态模板页,test.js是一个js文件,而tx.jpg是一张图片。各代码如下:


index.page
<html>
<head>
<script type="text/javascript" src="/js/test.js"></script>
</head>
<body>
<h1>Hello,${name}</h1>
<img src="/pic/tx.jpg" title="tx" />
<script type="text/javascript">
hello();
</script>
</body
<html>

test.js
function hello(){
console.log("hello!")
}

下边我们启动项目,输出如下:

可以看到启动成功,用浏览器打开:http://localhost:8899/index.page?name=guojing,发现响应页面如下:

五、总结

至此版本learn-1完成,基于该项目我们已经能够实现一个简单的网站,但是也就只是比纯静态网站多了页面数据渲染,并不能真正的实现动态交互。那么如何才能做到呢?答案就是提供session支持,下一版本我们将加入session支持,是其更加完善。    
  最后附上源码(github)地址:源代码

打造一款属于自己的web服务器——从简单开始,布布扣,bubuko.com

时间: 2024-10-25 08:05:17

打造一款属于自己的web服务器——从简单开始的相关文章

打造一款属于自己的web服务器——最后的一点完善

上一篇我们通过反射实现了动态加载多个controller,就功能上来说整个项目已经基本上完成了,但是目前我们仍然还有一些问题,例如模板支持不好.很多配置信息硬编码不好修改.此外,我们预期的目标是实现一个可嵌入的jar,以实现web服务,而就目前而言明显是不行的.那么我们现在就来解决这些问题. 一.使用velocity拓展模板    想要实现一套完善的模板还是比较麻烦的,所以目前我们考虑使用java支持的模板来实现,目前比较常用的有Freemaker,Velocity等,因为比较熟悉velocit

打造一款属于自己的web服务器——实现Session

上一次我们已经实现了一个简单的web服务器版本,能够实现一些基本功能,但是在最后也提到了这个版本由于不支持session并不能实现真正的动态交互,这一次我们就来完成这一功能. 一.Session实现原理 凡是搞过web开发的都知道,多数情况下浏览器请求服务器使用的是http请求,而http请求是无状态的,也就是说每次请求服务器都会新建连接,当得到响应后连接就关闭了,虽然http1.1支持持久连接(keep-alive),但是其最用主要是避免每次重建连接,而非解决用户在线状态等业务上的需求.而如果

打造一款属于自己的web服务器——配置controller

这天一热,胖子的的世界就是一片凄惨啊,随便动动身子,就跟洗个澡似得,心情固然烦躁,一烦躁就很难静下心来写东西了......所以这一段没咋用心写,就稍微水点吧,同时,我又打算要减肥了!>_<!. 上一次我们介绍了session的实现,使web服务器(现在总觉得准确来说应该叫可独立部署的web框架,称不上服务器)具备了基本功能,但是仔细一想就会发现一个严重的问题:每当实现一个新的controller,那么就需要在invokController方法里边增加判断,以便url能够找到对应controll

Nodejs搭建web服务器实现简单的登陆

本服务器采用了node-koa(2.0):koa是Express下的一个web框架. 搭建步骤: 1.首先我们创建一个web-koa目录,在目录下创建app.js,package.json,start.js三个文件. 2.然后我们来编辑最基本的package.json文件,如下: $ npm init { "name": "myapp", "version": "1.0.0", "description":

分别使用Nginx反向代理和Haproxy调度器实现web服务器负载均衡

1.1 使用nginx实现静态分离得负载均衡集群 1.1.1 Nginx负载均衡基础知识 本实验使用的主机是: 主机 IP地址 角色 centos23.cn 192.168.3.23 Nginx反向代理服务器 centos24.cn 192.168.3.24 web服务器 centos25.cn 192.168.3.25 web服务器 网络拓扑图: Nginx的upstream负载的5种方式,目前最常用得前3种方式 1.轮询(默认) 每个请求按时间顺序逐一分配到不同得后端服务器,如果后端服务器d

WEB服务器与应用服务器的区别

一.简述 WEB服务器与应用服务器的区别: 1.WEB服务器: 理解WEB服务器,首先你要理解什么是WEB?WEB你可以简单理解为你所看到的HTML页面就是WEB的数据元素,处理这些数据元素的应用软件就叫WEB服务器,如IIS.apache. WEB服务器与客户端打交道,它要处理的主要信息有:session.request.response.HTML.JS.CS等. 2.应用服务器: 应用服务器如JSP,处理的是非常规性WEB页面(JSP文件),他动态生成WEB页面,生成的WEB页面在发送给客户

Web服务器和应用服务器简介

通俗的讲,Web服务器传送页面使浏览器可以浏览,然而应用程序服务器提供的是客户端应用程序可以调用(call)的方法(methods).确切一点,你可以说:Web服务器专门处理HTTP请求(request),但是应用程序服务器是通过很多协议来为应用程序提供(serves)商业逻辑(business logic). 1.应用服务器处理业务逻辑,web服务器则主要是让客户可以通过浏览器进行访问. 2.应用服务器处理业务逻辑,web服务器是用于处理HTML文件的.web服务器通常比应用服务器简单,如ap

几种常见的Web服务器

Apache与Tomcat的区别 ,几种常见的web/应用服务器 APACHE是一个web服务器环境程序 启用他可以作为web服务器使用 不过只支持静态网页 如(asp,php,cgi,jsp)等动态网页的就不行 如果要在APACHE环境下运行jsp 的话就需要一个解释器来执行jsp网页 而这个jsp解释器就是TOMCAT, 为什么还要JDK呢?因为jsp需要连接数据库的话 就要jdk来提供连接数据库的驱程,所以要运行jsp的web服务器平台就需要APACHE+TOMCAT+JDK 整合的好处是

Web服务器原理及简单实现

Web系统由客户端(浏览器)和服务器端两部分组成.Web系统架构也被称为B/S架构.最常见的Web服务器有Apache.IIS等,常用的浏览器有IE.Firefox.chrome等.当你想访问一个网页时,需要在浏览器的地址栏中输入该网页的URL(Uniform Resource Locator,简称为URL)地址,或者是通过超链接链接到该网页.浏览器会向该网页所在的服务器发送一个HTTP请求,服务器会对接收到的请求信息进行处理,然后将处理的结果返回给浏览器,最终将浏览器处理后的结果呈现给用户.