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

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

1、准备工做

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

  • java开发环境,IDE依据我的爱好,JDK1.6+(1.6以后才自带httpserver)
  • maven环境,项目使用maven构建
  • git,若是你想clone个人代码作参考的话,固然github也支持直接下载zip包

、功能和结构设计

   咱们动手写代码以前应该先肯定好项目的功能,并设计好项目的结构,虽然咱们目前须要实现的很简单,可是仍是应该简单的进行一番设计。项目计划的功能以下:html5

  • 基本的http请求接收和响应
  • 能够分别处理动态和静态资源请求,对于静态请求直接返回对应资源,对于动态请求处理后返回
  • 简单的模板处理,经过替代符替换的方法实现模板数据渲染
  • 简单的log支持,不是必须,可是却颇有用

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

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

3、实现代码

一、新建并配置项目

    首先咱们须要新建一个maven项目,首先创建以下结构: github

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

  • 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以下:
apache

<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

二、实现主服务类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();
    }
}
View Code

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

三、实现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);
        }
    }
View Code

    能够看到首先根据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;
    }
}
View Code

    在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();
    }
View Code

4、测试项目

    至此,咱们已经完成了以前预期的功能,如今咱们来测试一下到底可否运行。咱们在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,发现响应页面以下:

5、总结

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

相关文章
相关标签/搜索