自己动手实现简单web容器一

一、前言

    主流系统架构无非分两种:C/S(客户端/服务器)、B/S(浏览器/服务器)

    1、C/S架构

        优点:安全

        缺点:升级维护成本高,一旦要升级,所有终端要逐个升级

    2、B/S架构

        优点:升级维护、部署简单,只需维护服务端即可

        缺点:安全性有所欠缺

    当下B/S无疑是系统开发中主流模式,在B/S架构流行的前提下,就JavaEE规范下出现了各种各样的Web框架,如Struts2、SpringMVC,而这些框架,无不基于Servlet规范,而Servlet容器中几乎没有不基于HTTP协议的实现(当然本文讨论的不是HTTP协议),在开发中我们处处使用框架,忽略底层细节,在部署时,打包扔进Tomcat、Jetty等web容器,简单方便。那么这些web容器的底层实现又是如何的呢?

    今天本文就展示如何一步步实现一个简单web服务器。

二、需求

    1、实现一个web容器,并模拟登陆操作

    2、可并发

    3、线程安全

三、技术点

    1、Socket编程

    2、Java IO

    3、线程池

    4、ThreadLocal

    5、多线程

    6、NIO、BIO

四、单线程模式实现 

    1、写一个HTML页面,里面有登陆需要的用户名、密码等输入元素,值得一提的是,本例使用的是GET方式提交请求,已经足够说明问题了,必须提到的是GET和POST获取参数的方式是有区别的。

<html>
	<head>
	<meta charset="UTF-8">
	<title>登录页</title>
	</head>
	<body>
		<form action="login" method="get">
			<table>
				<tr>
					<td>用户名:</td>
					<td><input type="text" name="username" ></td>
				</tr>
				<tr>
					<td>密码:</td>
					<td><input type="text" name="password" ></td>
				</tr>
				<tr>
					<td><input type="reset" value="重置"></td>
					<td><input type="submit" value="登录"></td>
				</tr>
			</table>
		</form>
	</body>
</html>

     2、有了HTML页面,那么如何把这个页面读到程序里呢,这个时候就要用到IO了,请看下面的方法:

// 为了方便main方法调用,并声明为static方法
private static String getLoginPage() {
	// 用于存储从文件读出的文件
	StringBuilder sb = new StringBuilder();
	try {
		// 声明一个BufferedReader准备读文件
		BufferedReader htmlReader = new BufferedReader(new InputStreamReader(
				new FileInputStream(new File(System.getProperty("user.dir") + "/webapp/login.html"))));
		String html = null;
		// 按行读取,直到最后一行结束
		while ((html = htmlReader.readLine()) != null) {
			sb.append(html);
		}
		// 关闭
		htmlReader.close();
	} catch (Exception e) {
		e.printStackTrace();
	}
	// 以字符串形式返回读到的HTML页面
	return sb.toString();
}

    3、那么服务端怎么与浏览器端交互,那么需要Socket,请看下面例子:

    (1)、先定义要返回的响应头

// HTTP请求的返回头
private static final StringBuilder responseContent = new StringBuilder();
static {
	responseContent.append("HTTP/1.1 200 OK");
	responseContent.append("Content-Type: text/html; charset=utf-8");
	responseContent.append("Content-Length:%s");
	responseContent.append("Date:%s");
	responseContent.append("Server:This is a simulation web container");
}

    (2)、启动web服务

public static void main(String[] args) {
	try {
		// 监听在8080端口
		ServerSocket serverSocket = new ServerSocket(8080);
		while (true) {
			// 接收请求
			Socket accept = serverSocket.accept();
			// 获取请求中的流
			BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
			// 只读取第一行数据
			String request = br.readLine();
			String result = "";
			if (!"".equals(request) && null != request) {
				String urlAndPrams = request.split(" ")[1];
				if (urlAndPrams.indexOf("/login") != -1) {
					// 获取用户传过来的用户名密码
					String[] params = urlAndPrams.split("\\?")[1].split("&");
					String username = params[0].split("=").length < 2 ? null : params[0].split("=")[1];
					String password = params[1].split("=").length < 2 ? null : params[1].split("=")[1];
					if ("admin".equals(username) && "admin".equals(password)) {
						result = "login success";
					} else {
						result = "username or password error";
					}
				} else {
					// 调用前1面定义的获取登录页方法,获取已经写好的登录页
					result = getLoginPage();
				}
			} else {
				// 调用前面定义的获取登录页方法,获取已经写好的登录页
				result = getLoginPage();
			}
			// 拿到该请求对应Socket的输出流,准备向浏览器写数据了
			PrintWriter printWriter = new PrintWriter(accept.getOutputStream());
			// 把文件长度、日期填回Response字符串中
			printWriter.println(String.format(responseContent.toString(), result.length(), new Date()));
			// 必须有个换行,换行之后才能向浏览器端写要传回的数据(HTTP协议)
			printWriter.println();
			// 写页面数据
			printWriter.println(result);
			printWriter.flush();
			printWriter.close();
			accept.close();
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}

    (3)、说明

    上一步代码,只读一行数据   拿到用户传过来的参数,其实是可以拿到HTTP请求的所有信息的,如下:

GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Cookie: _ga=GA1.1.1755609982.1449039124; CNZZDATA1258600948=1953676425-1469624561-%7C1469624561; CNZZDATA1258740907=123042277-1469628131-%7C1469628131; userName=admin

五、执行效果

    1、启动程序,浏览器器敲下http://localhost:8080/,回车,便能看到如下界面

      180249_HGSh_1778239.png

    2、输入用户名:admin,密码:admin,回车,提示登录成功

    

    3、输入错误用户名或密码

     

六、后记

    至此,一个单线程的web服务完成,其中存在如下问题

    1、只能处理单个用户请求,多用户无法同时使用

    2、如何实现并行处理用户请求

    3、并行时线程如何安全

    4、阻塞式的IO效率低,如何改进

    请期待下一篇《自己动手实现简单web容器二》

    快乐源于分享。

   此博客乃作者原创, 转载请注明出处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值