《JSP和Servlet那些事儿 》系列文章旨在阐述Servlet(Struts和Spring的MVC架构基础)和JSP内部原理以及一些比较容易混淆的概念(比如forward和redirect区别、静态include和<jsp:include标签区别等)和使用,本文为系列文章之启蒙篇--初探HTTP服务器,基本能从本文中折射出Tomcat和Apache HTTPD等处理静态文件的原理。敬请关注连载!
在学习Servlet和JSP的过程中,如果对HTTP协议本身以及HTTP服务器运行原理有初步的认识的话,这会使得后边的学习更加容易。HTTP服务器本身的内部原理对于Java而言是比较简单的,就是一个Socket处理;请求的解析就是Socket InputStream的读取和分析,所谓的响应仅仅是按照HTTP协议规定的顺序把字节流写入到Socket OutputStream里面。以下是一个简单的HTTP服务器,希望读者在阅读代码的过程中能够想起RFC2616的一些相关术语、或者能够很容易的理解代码。
一个简单的HTTP服务器需要注意以下几点 :
1,HTTP服务器监听主机和端口:也就是Java Socket的监听主机和端口
2,DocRoot:也就是文档根路径,就是http服务器查找客户端请求资源的根路径。
3,处理线程:需要有至少一个处理线程用于解析Socket输入流及回写请求到Socket输出流,Socket输出流就是发给客户端的通道
4,以上配置最好提供配置文件(类似具有HTTP服务功能的tomcat配置文件conf/server.xml,Apache HTTPD的httpd.conf文件)
根据以上几点,Java实现基本的HTTP服务器的思路如下 :
1,需要写一个类,代码全局的HTTP服务器配置(端口,线程数等);对应本文中Configure类
2,需要一个Main类,实质就是主线程,主线程需要绑定ServerSocket用于监听客户端请求,并启动多个处理线程处理客户端Socket请求; 对应本文中的HttpServer类
3,需要多个处理线程,用于处理主线程分配的Socket处理任务; 对应本文中的ProcessThread类
4,需要一个专门用于解析HTTP请求的类,该类从Socket中获取到输入流,然后读取输入流中的字节,从而解析出客户端希望请求的资源; 对应本文中的HttpRequest类
5,需要一个专门回写请求的类,把客户端请求的资源对应的文件流输出到Socket的输出流,如果资源找不到的话,就返回404给客户端; 对应本文中的HttpResponse类
以下是全部的代码:
常量类:
主要定义了一些常量,比如HTTP服务器默认的监听主机和端口、默认的文档根路径、默认处理线程数、默认配置文件等。
package lesson1.server; import java.util.HashMap; public final class Constants { /** * Listener's default values. */ public final static String DEFAULT_HOST = "localhost"; public final static int DEFAULT_PORT = 8080; public final static String DEFAULT_DOC_ROOT = "./webapps"; /** * Default work thread count. */ public final static int DEFAULT_WORKER_COUNT = 10; public static final byte CR = (byte) '\r'; public static final byte LF = (byte) '\n'; public static final byte SP = (byte) ' '; public static final byte HT = (byte) '\t'; public static final String CRLF = "\r\n"; public static final byte COLON = (byte) ':'; public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1"; public static final int HTTP_CODE_200 = 200; public static final int HTTP_CODE_403 = 403; public static final int HTTP_CODE_404 = 404; public static final int HTTP_CODE_500 = 500; public static final int HTTP_CODE_503 = 503; /** * 定义HTTP Response Code对应的Message */ public static HashMap<Integer, String> CODE2MESSAGE = new HashMap<Integer, String>(); static{ CODE2MESSAGE.put(HTTP_CODE_200, "OK"); CODE2MESSAGE.put(HTTP_CODE_403, "Forbidden"); CODE2MESSAGE.put(HTTP_CODE_404, "Not Found"); CODE2MESSAGE.put(HTTP_CODE_500, "Internal Server Error"); CODE2MESSAGE.put(HTTP_CODE_503, "Service Unavailable"); } /** * 定义MIME Type */ public static HashMap<String, String> MIMETYPES = new HashMap<String, String>(); static{ MIMETYPES.put("html", "text/html"); MIMETYPES.put("htm", "text/html"); MIMETYPES.put("txt", "text/plain"); MIMETYPES.put("xml", "application/xml"); MIMETYPES.put("js", "text/javascript"); MIMETYPES.put("css", "text/css"); MIMETYPES.put("jpe", "image/jpeg"); MIMETYPES.put("jpeg", "image/jpeg"); MIMETYPES.put("jpg", "image/jpeg"); } public static final String DEFAULE_CONFIG_FILE ="server.properties"; public static final String CONFIG_HOST = "host"; public static final String CONFIG_PORT ="port"; public static final String CONFIG_DOCROOT ="docRoot"; public static final String CONFIG_THREAD_COUNT ="threadCount"; }
全局配置类:
该类主要负责从配置文件中读取到HTTP服务器的监听主机、端口、DocRoot等重要信息。HTTP服务器的其他代码全部都会应用这个类的属性。
package lesson1.server; import java.io.FileInputStream; import java.io.InputStream; import java.util.Properties; /** * HTTP服务器全局配置项 * * @author sta * */ public class Configure { // listening host private String host = Constants.DEFAULT_HOST; // listening port private int port = Constants.DEFAULT_PORT; // Document Root which locate the static resource private String docRoot = Constants.DEFAULT_DOC_ROOT; /** * Http Server config file path */ private String configFile = Constants.DEFAULE_CONFIG_FILE; /** * Worker thread count. */ private int workerCount = Constants.DEFAULT_WORKER_COUNT; // 发送HTTP响应的缓冲区大小 private static final int DEFAULT_SEND_BUFFER_SIZE = 8 * 1024; // default 8k private int sendBufferSize = DEFAULT_SEND_BUFFER_SIZE; private static final Configure instance = new Configure(); // for singleton private Configure() { Properties properties = new Properties(); InputStream in = null; try { in = new FileInputStream(configFile); properties.load(in); setHost(properties.getProperty(Constants.CONFIG_HOST)); setPort(Integer.parseInt(properties.getProperty(Constants.CONFIG_PORT))); setDocRoot(properties.getProperty(Constants.CONFIG_DOCROOT)); setWorkerCount(Integer.parseInt(properties .getProperty(Constants.CONFIG_THREAD_COUNT))); } catch (Exception e) { System.out.println("Failed to load the config from file[" + configFile + "], Http Server will use the default config."); e.printStackTrace(); } finally { try { in.close(); } catch (Exception e) { } } } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getDocRoot() { return docRoot; } public void setDocRoot(String docRoot) { this.docRoot = docRoot; } public static Configure getInstance() { return instance; } public int getWorkerCount() { return workerCount; } public void setWorkerCount(int workerCount) { this.workerCount = workerCount; } public int getSendBufferSize() { return sendBufferSize; } public void setSendBufferSize(int sendBufferSize) { this.sendBufferSize = sendBufferSize; } }
Main类:
该类中定义了一个队列(taskQueue)用于保存客户端请求对应的Socket;
初始化方法中启动多个处理线程,并同时把taskQueue的引用传递给处理线程;
run方法主要是绑定ServerSocket监听,然后while循环不断接受客户端请求,客户端请求对应的socket全部保存到taskQueue队列中,然后被处理线程取走进行处理。
package lesson1.server; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.LinkedBlockingQueue; /** * This class is the mock HTTP Server which can only process static * resource(*.html,*.js,*.css etc.). * * @author sta * */ public class HttpServer { /** * HttpServer will add socket to this queue, ProcessThread will get task * from this queue. */ private LinkedBlockingQueue<Socket> taskQueue = new LinkedBlockingQueue<Socket>(); /** * @param args */ public static void main(String[] args) { HttpServer instance = new HttpServer(); try { instance.init(); instance.run(); } catch (Exception e) { e.printStackTrace(); } } /** * Init method mainly start the worker thread. */ private void init() { // use fixed thread to processing message for (int i = 0; i < Configure.getInstance().getWorkerCount(); i++) { // Use "taskQueue" as the constructor parameter, ProcessThread will // block at getting task util server get task. Thread processThread = new ProcessThread(taskQueue); processThread.setName("HttpServer-ProcessThread" + i); processThread.start(); } System.out.println(Configure.getInstance().getWorkerCount() + " work thread had been started."); } /** * Bind the socket to specified host and port, waiting the connection from * client. * * @throws Exception */ private void run() throws Exception { InetSocketAddress address = new InetSocketAddress(Configure .getInstance().getHost(), Configure.getInstance().getPort()); ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(address); System.out.println("Server is listening on Host[" + Configure.getInstance().getHost() + "],Port[" + Configure.getInstance().getPort() + "]"); System.out.println("Server is waiting the connection from Client."); while (true) { Socket s = serverSocket.accept(); // just only add the Socket into taskQueue,the worker threads will get // this socket and process it. taskQueue.add(s); } } }
处理线程类:
处理线程持有以上主线程的taskQueue引用,然后不断从该队列中获取socket,获得socket之后便实例化HttpRequest和HttpResponse对象,并调用HttpRequest的parse方法进行请求解析,根据解析结果查找本地资源,如果请求的资源存在,那么就把本地资源对应的流传递给HttpResponse,由HttpResponse进行读取,HttpResponse读取到的流全部回写到客户端,从而完成请求。
package lesson1.server; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.concurrent.LinkedBlockingQueue; /** * This is the main thread which will read resource from server machine, then * return the byte to client. * * @author sta * */ public class ProcessThread extends Thread { LinkedBlockingQueue<Socket> queue = null; public ProcessThread(LinkedBlockingQueue<Socket> queue) { this.queue = queue; } @Override public void run() { while (true) { HttpRequest request = null; HttpResponse response = null; InputStream inStream = null; OutputStream outStream = null; Socket socket = null; try { socket = queue.take(); inStream = socket.getInputStream(); request = new HttpRequest(inStream); outStream = socket.getOutputStream(); response = new HttpResponse(outStream); // 解析请求消息 try { request.parse(); response.setHttpRequest(request); } catch (IOException e) { } /* * HTTP服务器真正处理逻辑,主要是: 1,根据URI查找响应的流 2,把流输出给客户端 */ String uri = request.getUri(); File resourceFile = new File(Configure.getInstance() .getDocRoot() + uri); if (!resourceFile.exists() || !resourceFile.canRead()) { response.setStatus(Constants.HTTP_CODE_404); } else { response.setStatus(Constants.HTTP_CODE_200); response.setResource(new FileInputStream(resourceFile)); } response.send(); } catch (Exception e) { response.setStatus(Constants.HTTP_CODE_500); try { response.send(); } catch (IOException e1) { e1.printStackTrace(); } } finally { try { inStream.close(); } catch (Exception e2) { } try { outStream.close(); } catch (Exception e2) { } // 默认对socket进行关闭 try { socket.close(); } catch (Exception e2) { } } } } }
请求解析类:
HTTP请求类的职责很简单,就是从输入流中读取字节,解析出请求的资源名称。其中parseRequestLine方法就是最重要的处理逻辑,这个基本是参照tomcat来实现的。
package lesson1.server; import java.io.IOException; import java.io.InputStream; /** * 代表HTTP请求,主要包含: 1,HTTP方法 2,请求URI:也就是请求的资源 3,协议:区分HTTP协议版本 * * @author sta * */ public class HttpRequest { String method = "GET"; String uri; String protocol; InputStream in = null; public HttpRequest(InputStream in) { this.in = in; } public void parse() throws IOException { parseRequestLine(); } private void parseRequestLine() throws IOException { int start = 0; int pos = 0; byte chr = 0; // 1024 byte is enough for test. byte[] buf = new byte[1024]; in.read(buf); // ignore blank line do { chr = buf[pos++]; } while (chr == Constants.CR || chr == Constants.LF); pos--; start = pos; // parse HTTP Method boolean space = false; while (!space) { if (buf[pos] == Constants.SP) { space = true; method = new String(buf, start, pos - start); } pos++; } start = pos; // parse URI space = false; while (!space) { if (buf[pos] == Constants.SP) { space = true; uri = new String(buf, start, pos - start); } pos++; } start = pos; // parse protocol space = false; while (!space) { if (buf[pos] == Constants.SP || buf[pos] == Constants.CR || (buf[pos] == Constants.LF)) { space = true; protocol = new String(buf, start, pos - start); } pos++; } } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } }
响应处理类:
相应处理类主要是把HTTP状态码,和服务端找到的资源流回写到输出流中。此外,HTTP响应头中也包含了Content-Length属性.
package lesson1.server; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class HttpResponse { int status; OutputStream toClientStream = null; HttpRequest httpRequest = null; private InputStream resource = null; private String resourceType = "text/plain"; public void setHttpRequest(HttpRequest httpRequest) { this.httpRequest = httpRequest; } public HttpResponse(OutputStream out) { this.toClientStream = out; } public void send() throws IOException { writeStatusLine(); writeHeaderAndResponseBody(); } private void writeStatusLine() throws IOException { toClientStream.write(httpRequest.getProtocol().getBytes()); toClientStream.write(Constants.SP); toClientStream.write(String.valueOf(status).getBytes()); toClientStream.write(Constants.SP); toClientStream.write(Constants.CODE2MESSAGE.get(status).getBytes()); toClientStream.write(Constants.CRLF.getBytes()); // 没写HTTP响应消息(比如200对应的OK) } private void writeHeaderAndResponseBody() throws IOException { if (resource != null) { try { // Content-Length和Content-Type头 String contentLengthLine = "Content-Length: " + resource.available(); toClientStream.write(contentLengthLine.getBytes()); toClientStream.write(Constants.CRLF.getBytes()); String contentType = "Content-Type: " + Constants.MIMETYPES.get(resourceType); toClientStream.write(contentType.getBytes()); toClientStream.write(Constants.CRLF.getBytes()); // 头和消息体之间是两个回车换行符 toClientStream.write(Constants.CRLF.getBytes()); //HTTP响应消息体数据 byte[] bytePerTime = new byte[Configure.getInstance() .getSendBufferSize()]; int count = -1; while ((count = resource.read(bytePerTime)) > 0) { toClientStream.write(bytePerTime,0,count); toClientStream.flush(); } } catch (IOException e) { throw e; } finally { try { resource.close(); } catch (Exception e2) { } } }else{ toClientStream.flush(); } } public void setResource(InputStream resource) { this.resource = resource; } public void setResourceType(String fileSuffix) { this.resourceType = fileSuffix; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } }
以上代码基本具备了HTTP请求处理能力,为了尽可能的简化和描述出HTTP服务器最本质的东西,省略了很多处理(比如静态资源缓存,HTTP头处理等)。验证HTTP服务器步骤:
1,新建一个server.properties文件,并正确配置主机、端口等
---
host=localhost
port=8080
docRoot=./webapps
threadCount=10
2,需要保证server.properties文件中配置的docRoot目录存在,拷贝一些静态文件(html...)到docRoot目录下
3,java lesson1.server.HttpServer启动
4,通过浏览器输入http://$host:$port/$resourceName便可,其中resourceName为相对于server.properties中配置的docRoot目录的相对路径。可以使用firefox的httpwatch查看请求和响应的细节。
注:本文于2013年4月3号进行了一次修改维护,主要是解决一下问题:
1,HTTP响应格式不正确,特别是响应的状态码不正确、以及未设置content-type导致图片数据在浏览器可能不见的问题。
2,本次附上所有打包好的源代码,包括测试用静态文件
相关推荐
pandas whl安装包,对应各个python版本和系统(具体看资源名字),找准自己对应的下载即可! 下载后解压出来是已.whl为后缀的安装包,进入终端,直接pip install pandas-xxx.whl即可,非常方便。 再也不用担心pip联网下载网络超时,各种安装不成功的问题。
基于java的大学生兼职信息系统答辩PPT.pptx
基于java的乐校园二手书交易管理系统答辩PPT.pptx
tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl
Android Studio Ladybug 2024.2.1(android-studio-2024.2.1.10-mac.dmg)适用于macOS Intel系统,文件使用360压缩软件分割成两个压缩包,必须一起下载使用: part1: https://download.csdn.net/download/weixin_43800734/89954174 part2: https://download.csdn.net/download/weixin_43800734/89954175
有学生和教师两种角色 登录和注册模块 考场信息模块 考试信息模块 点我收藏 功能 监考安排模块 考场类型模块 系统公告模块 个人中心模块: 1、修改个人信息,可以上传图片 2、我的收藏列表 账号管理模块 服务模块 eclipse或者idea 均可以运行 jdk1.8 apache-maven-3.6 mysql5.7及以上 tomcat 8.0及以上版本
tornado-6.1b2-cp38-cp38-macosx_10_9_x86_64.whl
Android Studio Ladybug 2024.2.1(android-studio-2024.2.1.10-mac.dmg)适用于macOS Intel系统,文件使用360压缩软件分割成两个压缩包,必须一起下载使用: part1: https://download.csdn.net/download/weixin_43800734/89954174 part2: https://download.csdn.net/download/weixin_43800734/89954175
matlab
基于java的毕业生就业信息管理系统答辩PPT.pptx
随着高等教育的普及和毕业设计的日益重要,为了方便教师、学生和管理员进行毕业设计的选题和管理,我们开发了这款基于Web的毕业设计选题系统。 该系统主要包括教师管理、院系管理、学生管理等多个模块。在教师管理模块中,管理员可以新增、删除教师信息,并查看教师的详细资料,方便进行教师资源的分配和管理。院系管理模块则允许管理员对各个院系的信息进行管理和维护,确保信息的准确性和完整性。 学生管理模块是系统的核心之一,它提供了学生选题、任务书管理、开题报告管理、开题成绩管理等功能。学生可以在此模块中进行毕业设计的选题,并上传任务书和开题报告,管理员和教师则可以对学生的报告进行审阅和评分。 此外,系统还具备课题分类管理和课题信息管理功能,方便对毕业设计课题进行分类和归档,提高管理效率。在线留言功能则为学生、教师和管理员提供了一个交流互动的平台,可以就毕业设计相关问题进行讨论和解答。 整个系统设计简洁明了,操作便捷,大大提高了毕业设计的选题和管理效率,为高等教育的发展做出了积极贡献。
这个数据集来自世界卫生组织(WHO),包含了2000年至2015年期间193个国家的预期寿命和相关健康因素的数据。它提供了一个全面的视角,用于分析影响全球人口预期寿命的多种因素。数据集涵盖了从婴儿死亡率、GDP、BMI到免疫接种覆盖率等多个维度,为研究者提供了丰富的信息来探索和预测预期寿命。 该数据集的特点在于其跨国家的比较性,使得研究者能够识别出不同国家之间预期寿命的差异,并分析这些差异背后的原因。数据集包含22个特征列和2938行数据,涉及的变量被分为几个大类:免疫相关因素、死亡因素、经济因素和社会因素。这些数据不仅有助于了解全球健康趋势,还可以辅助制定公共卫生政策和社会福利计划。 数据集的处理包括对缺失值的处理、数据类型转换以及去重等步骤,以确保数据的准确性和可靠性。研究者可以使用这个数据集来探索如教育、健康习惯、生活方式等因素如何影响人们的寿命,以及不同国家的经济发展水平如何与预期寿命相关联。此外,数据集还可以用于预测模型的构建,通过回归分析等统计方法来预测预期寿命。 总的来说,这个数据集是研究全球健康和预期寿命变化的宝贵资源,它不仅提供了历史数据,还为未来的研究和政策制
基于微信小程序的高校毕业论文管理系统小程序答辩PPT.pptx
基于java的超市 Pos 收银管理系统答辩PPT.pptx
基于java的网上报名系统答辩PPT.pptx
基于java的网上书城答辩PPT.pptx
婚恋网站 SSM毕业设计 附带论文 启动教程:https://www.bilibili.com/video/BV1GK1iYyE2B
基于java的戒烟网站答辩PPT.pptx
基于微信小程序的“健康早知道”微信小程序答辩PPT.pptx
Capital Bikeshare 数据集是一个包含从2020年5月到2024年8月的自行车共享使用情况的数据集。这个数据集记录了华盛顿特区Capital Bikeshare项目中自行车的租赁模式,包括了骑行的持续时间、开始和结束日期时间、起始和结束站点、使用的自行车编号、用户类型(注册会员或临时用户)等信息。这些数据可以帮助分析和预测自行车共享系统的需求模式,以及了解用户行为和偏好。 数据集的特点包括: 时间范围:覆盖了四年多的时间,提供了长期的数据观察。 细节丰富:包含了每次骑行的详细信息,如日期、时间、天气条件、季节等,有助于深入分析。 用户分类:数据中区分了注册用户和临时用户,可以分析不同用户群体的使用习惯。 天气和季节因素:包含了天气情况和季节信息,可以研究这些因素对骑行需求的影响。 通过分析这个数据集,可以得出关于自行车共享使用模式的多种见解,比如一天中不同时间段的使用高峰、不同天气条件下的使用差异、季节性变化对骑行需求的影响等。这些信息对于城市规划者、交通管理者以及自行车共享服务提供商来说都是非常宝贵的,可以帮助他们优化服务、提高效率和满足用户需求。同时,这个数据集也