基于Java手写web服务器(简易版)
来源:互联网 发布:淘宝车秒贷首付怎么算 编辑:程序博客网 时间:2024/05/22 14:55
本人尚在java 学习阶段,不是技术大咖, 自认是技术宅, 有一点写东西的能力,因最近学习了java网络编程,决定手写一个web服务器,不喜勿喷,大神也请高抬贵手,不足之处还望指点一二,不胜感激!
本项目源码已上传到github,本文章结尾将奉上地址。
目录结构
项目文件目录,是基于Maven的标准文件夹目录,src内包含7个类和一个接口,user包下的类为使用类。一个config文件夹用于存放服务器配置文件,webapps文件夹为服务器站点目录。
WebServer类
此类为本服务器的main线程类,负责启动服务器,不断接收客户端连接,使用ServerSocket对象实现基于tcp的网络对接,因为
并发线程会很多,所以使用线程池来不断接收客户端的连接,也避免了频繁创建线程的效率低下问题。这里的端口号与线程池的大
小都写入了config配置文件中,便于后期修改配置。接收到的soket对象放入ClientHandler线程类中使用。
构造函数中初始化ServerSocket对象与线程池对象。
public WebServer() { try { server = new ServerSocket(ServerContext.Port); threadPool = Executors.newFixedThreadPool(ServerContext.MaxThread); } catch (IOException e) { e.printStackTrace(); System.out.println("服务器启动失败!"); } }}
写一个start方法不停循环接收浏览器请求,在调用ClientHandler线程类去处理浏览器请求。
public void start() { try { while (true) { System.out.println("等待客户端连接"); Socket socket = server.accept(); threadPool.execute(new ClientHandler(socket)); } } catch (Exception e) { } }
在main函数中启动服务端程序。
public static void main(String[] args) { WebServer server = new WebServer(); server.start(); }
ClientHandler类
此类为客户端线程类,实现Runnable接口,是http连接的核心类,负责接收浏览器端发来的请求并返回响应消息,中转站的作用
。
run方法首先获取输入输出流并使用HttpRequest对象解析请求。
InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream();
HttpRequset req = new HttpRequset(in);
boolean isPost = "POST".equals(req.getMethod()); HttpResponse res = new HttpResponse(out);
把消息分为静态内容和动态内容//--动态内容-- //是否为用户数据处理接口(动态内容) if (ActionListen.isAction(req.getUri(), isPost)) { ActionListen.doAction(req, res, req.getUri(), isPost); return;} //--静态内容-- //获取请求类型 String type = req.getUri().substring(req.getUri().lastIndexOf(".") + 1); //设置响应头 res.setHeader("Content-Type", ServerContext.getType(type)); //获取静态文件 File file = new File(ServerContext.WebRoot + req.getUri()); if (!file.exists()) { //404 请求内容找不到 res.setStatus(404); file = new File(ServerContext.WebRoot + "/" + ServerContext.NotFoundPage); } else { res.setStatus(200); } //响应静态内容 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); byte[] bys = new byte[(int) file.length()]; bis.read(bys); res.write(bys); bis.close();
HttpRequset类
解析http请求消息,存储资源标识符,请求类型和协议等,对请求头进行封装,保存所有的消息头序列,封装请求参数。写此类
前必须先了解http协议格式。
在analysis方法中首先解析uri、method、protocol属性,
String line = buffer.readLine(); if (line != null && line.length() > 0) { String[] temp = line.split("\\s"); this.method = temp[0]; this.uri = temp[1]; if (this.uri.indexOf("?") != -1) { String str = this.uri.substring(this.uri.indexOf("?") + 1); genarenal(str, false); this.uri = this.uri.substring(0, this.uri.indexOf("?")); } if (this.uri.endsWith("/")) { this.uri += "index.html"; } this.protocol = temp[2]; }
然后解析header信息,这里要注意的一个问题点是使用buffer.readLine时读取到末尾并不会自动退出,会一直阻塞,因为浏览器
端发送消息后或一直等待服务端的响应直到请求超时,所以在等待期间输入流会一直打开,而服务端使用buffer.readLine读取时,读
取到最后时因为浏览器端输入流没关闭,所以会一直阻塞,造成了两端在相互等待的情况,形成死锁。所以在读取到空行时就break
出while循环。这里保存的Content-Length的值是为了根据这个值的大小去读取post请求的内容。
String l = null; int len = 0; while ((l = buffer.readLine()) != null) {if ("".equals(l)) { break; } String k = l.substring(0, l.indexOf(":")).trim(); String v = l.substring(l.indexOf(":") + 1).trim(); this.Header.put(k, v); if (l.indexOf("Content-Length") != -1) { len = Integer.parseInt(l.substring(l.indexOf(":") + 1).trim()); } }
如果请求的为post请求,则根据Content-Length读取消息体
if (method != null && method.toUpperCase().equals("POST")) { char[] bys = new char[len]; buffer.read(bys); String paraStr = new String(bys); genarenal(paraStr, true); }
/** * 对请求字符串解析成参数对象 * @param str * @param isPost */ private void genarenal(String str, boolean isPost) { String[] arr = str.split("&"); for (String s : arr) { String[] temp = s.split("="); if (isPost) { this.Form.put(temp[0], temp[1]); } else { this.QueryString.put(temp[0], temp[1]); } this.Parameter.put(temp[0], temp[1]); } }
HttpResponse类
4个属性 :Status存储所有消息响应码与对应的内容,Header则是存储用于响应的所有消息头,整型status用于设置本次响应码
out流为响应流。
初始化属性:
public HttpResponse(OutputStream out) { this.out = out; Header = new LinkedHashMap<String, String>(); Status = new HashMap<Integer, String>(); Status.put(HttpContext.STATUS_CODE_OK, HttpContext.STATUS_REASON_OK); Status.put(HttpContext.STATUS_CODE_NOT_FOUND, HttpContext.STATUS_REASON_NOT_FOUND); Status.put(HttpContext.STATUS_CODE_ERROR, HttpContext.STATUS_REASON_ERROR); Header.put("Content-Type", "text/plain;charset=utf-8"); Header.put("Date", new Date().toString()); status = 200; }
两个核心重载方法分别用于发送字节流和字符串
/** * 响应方法,发送字符串 * @param bys */ public void write(String str) { Header.put("Content-Length", Integer.toString(str.length())); PrintStream ps = new PrintStream(out); printHeader(ps); ps.println(str); ps.flush(); } /** * 打印头信息 * @param ps */ private void printHeader(PrintStream ps) { ps.println(ServerContext.Protocol + " " + status + " " + Status.get(status)); Set<Entry<String, String>> set = Header.entrySet(); for (Entry<String, String> entry : set) { String k = entry.getKey(); String v = entry.getValue(); ps.println(k + ":" + v); } ps.println(""); }
HttpContext类
静态变量类,目前用于存放响应状态码和状态信息。
public class HttpContext { public final static int STATUS_CODE_OK = 200; public final static String STATUS_REASON_OK = "OK"; public final static int STATUS_CODE_NOT_FOUND = 404; public final static String STATUS_REASON_NOT_FOUND = "Not Found"; public final static int STATUS_CODE_ERROR = 500; public final static String STATUS_REASON_ERROR = "Internal Server Error";}
ServerContext类
读取配置文件类,首先在config文件夹中创建一个xml文件,存放配置信息
这里使用dom4j来读取xml文件
private static void init() { Types = new HashMap<String, String>(); try { SAXReader reader = new SAXReader(); Document doc = reader.read("config/config.xml"); Element root = doc.getRootElement(); Element service = root.element("service"); Element connector = service.element("connector"); Protocol = connector.attributeValue("protocol"); Port = Integer.parseInt(connector.attributeValue("port")); MaxThread = Integer.parseInt(connector.attributeValue("max-thread")); WebRoot = service.elementText("webroot"); NotFoundPage = service.elementText("not-found-page"); @SuppressWarnings("unchecked") List<Element> typeMappings = root.element("type-mappings").elements("type-mapping"); for (Element e : typeMappings) { Types.put(e.attributeValue("ext"), e.attributeValue("type")); } } catch (Exception e) { e.printStackTrace(); } }
-----到此,本服务器大致已经搭建完成,可以请求响应静态内容,如html、js、css、图片等内容,接下来,我们开始动态内容的搭建
首先,写一个action.xml存放与config目录下,基本结构如下,主要放请求url,使用的请求方法和处理此请求的类方法。
再写一个接口用于规范处理数据类,
public interface Action {void bridge(HttpRequset req, HttpResponse res);}
ActionListen类
用于读取action.xml中的信息使用的也是dom4j来读取。
private static Map<String, String> GetMethod;private static Map<String, String> PostMethod;static {loadListenes();}private static void loadListenes() {GetMethod = new HashMap<String, String>();PostMethod = new HashMap<String, String>();try {SAXReader reader = new SAXReader();File file = new File("config/action.xml");Document doc = reader.read(file);Element root = doc.getRootElement();@SuppressWarnings("unchecked")List<Element> list = root.elements("listen");for (Element e : list) {Element action = e.element("action");if ("POST".equals(action.attributeValue("method").toUpperCase())) {PostMethod.put(action.getText(), e.elementText("target"));} else {GetMethod.put(action.getText(), e.elementText("target"));}}} catch (Exception e) {e.printStackTrace();}}
使用反射执行xml里的处理类方法:
public static void doAction(HttpRequset req, HttpResponse res, String methodurl, boolean isPost) {try {String target = null;if (isPost) {target = PostMethod.get(methodurl);} else {target = GetMethod.get(methodurl);}Class<? extends Object> cls = Class.forName(target);Object obj = cls.newInstance();Method[] methods = cls.getDeclaredMethods();for (Method method : methods) {if ("bridge".equals(method.getName())) {method.invoke(obj, req, res);}}} catch (Exception e) {e.printStackTrace();}}
首先在webapps文件夹里写一个login.html文件
<div class="main"><h2>登录</h2><form id="myform"><table><tr><td>用户名:</td><td><input type="text" name="user" id="user"></td></tr><tr><td>密 码:</td><td><input type="password" name="password" id="pwd"></td></tr><tr><td colspan="2"><input type="submit" value="提交"> <a href="register.html">还没帐号?现在注册</a></td></tr></table></form></div><script type="text/javascript">var form = document.getElementById('myform');form.onsubmit = function (e) {var user = document.getElementById('user').value;var pwd = document.getElementById('pwd').value;var data = "user="+user+"&password="+pwd;dosome(data, function (rel){alert(rel);});e.preventDefault();}function dosome (param, callback) {var xhr = new XMLHttpRequest();xhr.open("POST", "http://localhost:8080/login", true);xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");xhr.send(param);xhr.onreadystatechange = function () {if (xhr.readyState == 4 && xhr.status == 200) {callback(xhr.responseText);}}}</script>
这里我用ajax请求模拟登录操作
在user包中创建Lodin类,如下:
public class Login implements Action{public void bridge(HttpRequset req, HttpResponse res) {String user = req.Form("user");String pwd = req.Form("password");String rel = user + "--" + pwd;res.write(rel);}}
测试:
测试成功!
本例源码:https://github.com/zhenxin77520/JavaServer
阅读全文
0 0
- 基于Java手写web服务器(简易版)
- 手写简易WEB服务器
- Java基于TCP的简易Web浏览器和服务器
- 简易版基于Java的处理静态资源服务器实现
- 简易版基于Java的处理静态资源服务器实现
- JAVA简易WEB服务器(一)
- JAVA简易WEB服务器(二)
- JAVA简易WEB服务器(三)
- JAVA简易WEB服务器(四)
- JAVA简易WEB服务器(五)
- JAVA-手写服务器
- java 手写服务器
- 简易Web服务器
- 简易Web服务器
- 简易web服务器
- 简易Web服务器实现
- 简易Web服务器实现
- 简易web服务器
- synchronized 用在实例方法和类方法的区别
- 运营思略:APP线上运营方案 | 应用商店付费篇
- 写入GPS信息到jpeg格式的图片中 ExifInterface类的使用
- mysql 存储过程
- ROS RRT RRTstar的实现
- 基于Java手写web服务器(简易版)
- 一条命令重启挂掉的docker
- VC++6.0 MFC对话框操作MySQL数据库的各种问题
- C语言笔记 指针 数组
- values资源之array和RecyclerView的使用
- tomcat8.5 managerApp 页面加载问题
- BZOJ1041: [HAOI2008]圆上的整点
- 数据结构课程设计——CET-6报名管理系统
- Expression封装