打造一款属于自己的web服务器——从简单开始
距离开篇已经过了很久,期间完善了一下之前的版本,目前已经能够完好运行,基本上该有的功能都有了,此外将原来的测试程序改为示例项目,新项目只需按照示例项目结构实现controller和view即可,详情见: easy-httpserver、 demo-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代码如下:
/**
* 主页对应的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服务器——从简单开始的更多相关文章
- 打造一款属于自己的web服务器——开篇
JVM总结慢慢来吧,先插播一篇水文,来介绍下最近业余一直在写的一个小项目——easy-httpserver(github).适合新手学习,大神们路过即可^_^. 一.这是个什么玩意? easy-htt ...
- 一篇文章带你了解轻量级Web服务器——Nginx简单入门
一篇文章带你了解轻量级Web服务器--Nginx简单入门 Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件代理服务器 在本篇中我们会简单介绍Nginx的特点,安装,相关指令使用以及配置信 ...
- IIS Web 服务器/ASP.NET 运行原理基本知识概念整理 转
转http://www.cnblogs.com/loongsoft/p/7272830.html IIS Web 服务器/ASP.NET 运行原理基本知识概念整理 前言: 记录 IIS 相 ...
- IIS Web 服务器/ASP.NET 运行原理基本知识概念整理
前言: 记录 IIS 相关的笔记还是从公司笔试考核题开始的,问 Application Pool 与 AppDomain 的区别? 促使我对进程池进了知识的学习,所以记录一下学习 ...
- JSP之WEB服务器:Apache与Tomcat的区别 ,几种常见的web/应用服务器
注意:此为2009年的blog,注意时效性(针对常见服务器) APACHE是一个web服务器环境程序 启用他可以作为web服务器使用 不过只支持静态网页 如(asp,php,cgi,jsp)等 ...
- 理解与模拟一个简单web服务器
先简单说下几个概念,根据自己的理解,不正确请见谅. web服务器 首先要知道什么是web服务器,简单说web服务器就是可以使用HTTP传输协议与客户端进行通信的服务器.最初的web服务器只能用来处理静 ...
- WEB服务器、应用程序服务器、HTTP服务器区别
很清晰的解释了WEB服务器.应用程序服务器.HTTP服务器区别 转载自 http://www.cnblogs.com/zhaoyl/archive/2012/10/10/2718575.html WE ...
- Web服务器原理及简单实现
Web系统由客户端(浏览器)和服务器端两部分组成.Web系统架构也被称为B/S架构.最常见的Web服务器有Apache.IIS等,常用的浏览器有IE.Firefox.chrome等.当你想访问一个网页 ...
- web服务器与应用服务器
WEB服务器与应用服务器的区别: 1.WEB服务器: 理解WEB服务器,首先你要理解什么是WEB?WEB你可以简单理解为你所看到的HTML页面就是WEB的数据元素,处理这些数据元素的应用软件就叫WEB ...
随机推荐
- 2017年3月14日-----------乱码新手自学.net 之Authorize特性与Forms身份验证(登陆验证、授权小实例)
有段时间没写博客了,最近工作比较忙,能敲代码的时间也不多. 我一直有一个想法,想给单位免费做点小软件,一切思路都想好了,但是卡在一个非常基础的问题上:登陆与授权. 为此,我看了很多关于微软提供的Ide ...
- UI2_同步下载
// // ViewController.m // UI2_同步下载 // // Created by zhangxueming on 15/7/17. // Copyright (c) 2015年 ...
- css常用操作
对齐操作 1.使用margin属性进行水平对齐 margin-left:auto; margin-right:auto; 2.使用position属性进行左右对齐 3.使用fl ...
- 单机版mongodb
1.下载安装包 wget http://fastdl.mongodb.org/linux/mongodb-linux-i686-1.8.2.tgz 下载完成后解压缩压缩包 tar zxf mongod ...
- RxJava四个基础接口
Publisher Subscriber Subscription Processor ----------------------------------- public interface Pub ...
- github入门之更改提交操作--6
1.回溯历史版本 1.1.回溯到创建feature-A分支前 1.1.1.要让仓库的HEAD.暂存区.当前工作树回溯到指定状态,需要用到提供目标时间点的哈希值 1.1.2.回溯至未创建feature- ...
- web端 第一天认识基础
.NET 分为两大类 一.客户端应用程序 C/S 技术: Winform WPF MFC MVVM 二.外部端应用程序 B/S(网页端应用程序/WEB端/WEB端应用程序) 目前学的技术是A ...
- 【Python图像特征的音乐序列生成】一个更科学的图片分类参考方法,以及一个看起来很好用的数据集
数据集地址:http://www.imageemotion.org/ 论文地址:http://www.doc88.com/p-1905670442096.html
- 【Python图像特征的音乐序列生成】生成伴奏旋律(附部分代码)
做了半天做的都是一些细枝末节的东西,嗨呀. 伴奏旋律是Ukulele和弦,MIDI发音乐器是Guitar.在弹唱的时候,Ukulele和弦就是伴奏. 我们以创建<成都>伴奏为例: 节奏型: ...
- UVA 1619 Feel Good 感觉不错 (扫描法)
Feel Good Time Limit: 3000MS Memory Limit: Unknown 64bit IO Format: %lld & %llu Bill is deve ...