《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,本次附上所有打包好的源代码,包括测试用静态文件
相关推荐
在IT行业中,JSP(JavaServer Pages)和Servlet是用于构建动态web应用程序的两种核心技术。本篇文章将深入探讨它们与HTTPS之间的联系,帮助你更好地理解如何在Java Web应用中实现安全的HTTPS通信。 首先,JSP是Java...
Jsp-Servlet复习笔记-----第3章 Servlet技术 - 堕落天使 - JavaEye技术网站.mhtJsp-Servlet复习笔记-----第3章 Servlet技术 - 堕落天使 - JavaEye技术网站.mht
"jakarta.servlet.jsp.jstl-api-2.0.0.jar"包含的是API部分,定义了各种标签接口和类,而"jakarta.servlet.jsp.jstl-2.0.0.jar"通常包含JSTL的具体实现。这两个JAR文件在开发和运行基于JSTL的应用时都是必不可少的。...
javax.servlet.jsp-api-2.3.1.jar
综上所述,`javax.servlet` jar包和`javax.servlet.jsp` jar包是Java Web开发不可或缺的部分,它们提供了处理HTTP请求的核心接口和类,以及JSP的实现。确保正确地引入和使用这些库,能帮助开发者构建功能丰富的Web...
`javax.servlet` 和 `jsp-api` 是Java服务器端编程的重要组成部分,主要用于构建动态Web应用程序。这两个API是Java Servlet和JavaServer Pages(JSP)技术的核心接口和类库,由Java Community Process (JCP)制定并由...
JSP文件在服务器上被编译为Servlet,然后由Servlet处理HTTP请求和响应。JSP的主要优点在于它将内容的展示和业务逻辑分离,提高了开发效率和代码可维护性。 **Servlet** 则是Java编写的一种服务器端程序,用于扩展...
Servlet-api.jar和jsp-api.jar是Java Web开发中两个非常重要的库文件,它们包含了Servlet和JSP(JavaServer Pages)的相关API,使得开发者可以构建动态Web应用程序。这两个文件通常由Java EE(Enterprise Edition)...
首先,系统的核心技术栈包括Java、JSP、Servlet、Tomcat和SQLServer,这些技术共同构建了系统的后端逻辑和数据存储。 Java作为基础,是系统开发的主要语言。它提供了丰富的类库和强大的面向对象特性,使得开发高效...
**JSP(JavaServer Pages)API 和 Servlet API 知识详解** JSP(JavaServer Pages)和Servlet是Java EE(Enterprise Edition)中的两个核心技术,它们主要用于构建动态Web应用程序。这两个API,即`jsp-api.jar`和`...
标题"jsp-api.jar和servlet-api.jar"提到了两个关键的Java Web开发中的库文件,它们是JavaServer Pages (JSP) 和Servlet技术的标准接口定义。这两个API是Java EE (Enterprise Edition) 平台的重要组成部分,用于构建...
基于 jsp + servlet + jquery + easy-ui + ajax 的学生成绩管理系统 基于 jsp + servlet + jquery + easy-ui + ajax 的学生成绩管理系统 基于 jsp + servlet + jquery + easy-ui + ajax 的学生成绩管理系统 基于 jsp...
《基于JSP和Servlet的JavaWeb项目:蛋糕店售卖网站》 该项目是一个典型的JavaWeb应用,主要用于模拟蛋糕店的商品展示和售卖过程。它基于JSP(JavaServer Pages)和Servlet技术构建,这两种技术是Java Web开发的核心...
javax.servlet.jsp.jstl-api-1.2.1.jar
web项目里面开发jsp页面的时候,使用action调用servlet里面get和post方法的时候需要这个jar包,来获取 请求路径的
标题“jsp-servlet全部lib-jar”表明这是一个与Java服务器页面(JSP)和Servlet相关的库集合,通常用于开发Web应用程序。这些库文件(jar格式)是Java开发工具箱的重要组成部分,尤其是对于构建基于Java的Web服务和...
Servlet-API和JSP-API是Java Web开发中两个至关重要的组件,它们构成了服务器端处理HTTP请求和呈现动态网页的基础。在本篇文章中,我们将深入探讨这两个API的用途、功能以及它们在Tomcat 7.0环境中的应用。 Servlet...