一个简单web服务器的java实现

一个简单的web服务器在不考虑其性能及健壮性的情况下,通常只需实现的功能包括服务器的启动,它用于监听某一个端口,接收客户端发来的请求,并将响应结果返回给客户端。本文将介绍一个简单web服务器的实现原理,它本身只能处理某个目录下的静态资源文件(文本、图片等)。采用java来实现的话,可以含以下几个模块,而且各个模块间的关系如图1所示。


图1、简单web服务器的模块

  •  HttpServer即为服务器,它用于服务器的启动及接收用户的请求并返回结果;
  • Request:对用户请求进行分析,解析请求串,并获取对应的访问url;
  • Response:根据用户请求生成响应结果,并将结果输出给客户端。

下面通过代码来具体的看下该服务器的实现过程。

HttpServer是服务器的主实现类,用于关联Request及Response,源代码如下所示:

[java]  view plain copy print ?
  1. package com.wow.server;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.io.InputStream;  
  6. import java.io.OutputStream;  
  7. import java.net.InetAddress;  
  8. import java.net.ServerSocket;  
  9. import java.net.Socket;  
  10. import java.net.UnknownHostException;  
  11.   
  12. /** 
  13.  * 一个简单的web应用服务器 
  14.  * @author zhaozheng 
  15.  * 
  16.  */  
  17. public class HttpServer {  
  18.   
  19.     public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";  
  20.     private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";  
  21.     private boolean shutdown = false;  
  22.       
  23.     public static void main(String[] args) {  
  24.         HttpServer server = new HttpServer();  
  25.         server.start();  
  26.     }  
  27.       
  28.     //启动服务器,并接收用户请求进行处理  
  29.     public void  start() {  
  30.         ServerSocket serverSocket = null;  
  31.         int  PORT = 8080;  
  32.         try {  
  33.             serverSocket = new ServerSocket(PORT, 1, InetAddress.getByName("127.0.0.1"));  
  34.         } catch (UnknownHostException e) {  
  35.             e.printStackTrace();  
  36.             System.exit(-1);  
  37.         } catch (IOException e) {  
  38.             e.printStackTrace();  
  39.             System.exit(-1);  
  40.         }  
  41.           
  42.         //若请求的命令不为SHUTDOWN时,循环处理请求  
  43.         while(!shutdown) {  
  44.             Socket socket = null;  
  45.             InputStream input = null;  
  46.             OutputStream output = null;  
  47.               
  48.             try {  
  49.                 //创建socket进行请求处理  
  50.                 socket = serverSocket.accept();  
  51.                 input = socket.getInputStream();  
  52.                 output = socket.getOutputStream();  
  53.                 //接收请求  
  54.                 Request request = new Request(input);  
  55.                 request.parser();  
  56.                 //处理请求并返回结果  
  57.                 Response response = new Response(output);  
  58.                 response.setRequest(request);  
  59.                 response.sendStaticResource();  
  60.                 //关闭socket  
  61.                 socket.close();  
  62.                 //若请求命令为关闭,则关闭服务器  
  63.                 shutdown = request.getUri().equals(SHUTDOWN_COMMAND);  
  64.             } catch (IOException e) {  
  65.                 e.printStackTrace();  
  66.                 continue;  
  67.             }  
  68.         }  
  69.           
  70.     }  
  71. }  

        该类本身是一个应用程序,包含main方法,直接通过java命令即可运行。在运行时,它本身会启动一个ServerSocket类用于监听服务器的某个端口。当接收到的命令不是停止服务器的SHUTDOWN时,它会创建一个Socket套接字,用于接收请求及返回响应结果。

        Request类则用于请求的接收,对于Http协议来讲,通过浏览器向服务器发送请求有一定的格式,其实Request也就是接收这些请求信息,并对其进行分析,抽取出所需的信息,包括cookie、url等。其中http发送的请求包括三部分:

  • 请求方法 统一资源标识符 协议/版本
  • 请求头
  •  请求实体

其中请求头与请求实体间有一个空行,具体的示例代码如下所示:

[html]  view plain copy print ?
  1. GET /index.htm HTTP/1.1   
  2. Host: www.baidu.com   
  3. Connection: keep-alive   
  4. User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11   
  5. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8   
  6. Accept-Encoding: gzip,deflate,sdch   
  7. Accept-Language: zh-CN,zh;q=0.8 Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3   
  8. Cookie: BAIDUID=D275C16E04D9BB2CF55FD9B9654AECAC:FG=1;   
  9. BDREFER=%7Burl%3A%22http%3A//news.baidu.com/%22%2Cword%3A%22%22%7D;BDUSS=FJ2SzJxSmpKMW1sdVIwMWw3TTBwaHhZRkxFaUdoeG9QLW5GS2dLTUtyZzNhWUpSQVFBQUFBJCQAAAAAAAAAAAouTQytzfcOemhhb3poZW5nNzc1OAAAAAAAAAAAAAAAAAAAAAAAAACAYIArMAAAALCmJHAAAAAA6p5DAAAAAAAxMC4zNi4xNDcblVA3G5VQdBDUT=5b2fD275C16E04D9BB2CF55FD9B9654AECAC138a495329b1MCITY=-%3A; shifen[3113720932]=1357565900; H_PS_PSSID=1445_1661  
        Request类用于接收socket套接字发送过来的字节流,并按照http协议请求的格式进行解析。对于简单的web服务器而言,我们只需要解析出它的uri即可,这样即可以通过通过文件匹配的方式找到对应的资源。具体的实现代码如下所示:

[java]  view plain copy print ?
  1. package com.wow.server;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5.   
  6. /** 
  7.    *接收到的请求串的具体格式如下: 
  8.    * GET /aaa.htm HTTP/1.1 
  9.    * Host: 127.0.0.1:8080 
  10.    * Connection: keep-alive 
  11.    * User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11 
  12.    * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,q=0.8 
  13.    * Accept-Encoding: gzip,deflate,sdch 
  14.    * Accept-Language: zh-CN,zh;q=0.8 
  15.    * Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3 
  16.    *  
  17.    * @author zhaozheng 
  18.    * 
  19.  */  
  20. public class Request {  
  21.   
  22.     private InputStream input;  
  23.     private String uri;  
  24.       
  25.     public Request(InputStream input) {  
  26.         this.input = input;  
  27.     }  
  28.       
  29.     public void parser() {  
  30.         StringBuffer request = new StringBuffer();  
  31.         byte[] buffer = new byte[2048];  
  32.         int i = 0;  
  33.           
  34.         try {  
  35.             i = input.read(buffer);  
  36.         } catch (IOException e) {  
  37.             e.printStackTrace();  
  38.             i = -1;  
  39.         }  
  40.           
  41.         for(int k = 0; k < i; k++) {  
  42.             request.append((char)buffer[k]);  
  43.         }  
  44.           
  45.         uri = parserUri(request.toString());  
  46.           
  47.     }  
  48.       
  49.     private String parserUri(String requestData) {  
  50.         int index1, index2;  
  51.         index1 = requestData.indexOf(' ');  
  52.         if(index1 != -1) {  
  53.             index2 = requestData.indexOf(' ', index1 + 1);  
  54.             if(index2 > index1) {  
  55.                 return requestData.substring(index1 + 1, index2);  
  56.             }  
  57.         }  
  58.           
  59.         return null;  
  60.     }  
  61.       
  62.     public String getUri() {  
  63.         return uri;  
  64.     }  
  65. }  

        通过上述代码可以看出parser方法用于解析具体请求,它接收socket的字节流,对其进行处理,获取其中的uri信息。

        当获取到uri信息后,即可将uri对应到服务器的某个应用或目录中。本文只是实现了一个简单的静态资源服务器,即将uri对应到某个目录下的文件,若文件存在则打开并读取文件信息; 若不存在则直接返回一段错误信息。Response类即用于处理该逻辑,同时它会将文件流写回至socket套接字中,由socket套接字将响应结果返回给客户端。

        对于http协议而言,响应也是有一定的格式要求的,不能发送任意格式的信息,否则浏览器是无法接收并处理的。Http响应结果也包括三部分:

  • 协议 状态码 描述
  • 响应头
  • 响应实体段

其中响应头与响应实体段间也是有一个空行的,具体的实例如下所示:

[html]  view plain copy print ?
  1. HTTP/1.1 200 OK   
  2. Date: Mon, 07 Jan 2013 14:31:36 GMT   
  3. Server: BWS/1.0   
  4. Content-Length: 4029   
  5. Content-Type: text/html;charset=gbk  
  6. Cache-Control: private Expires: Mon, 07 Jan 2013 14:31:36 GMT   
  7. Content-Encoding: gzip   
  8. Set-Cookie: H_PS_PSSID=1445_1661path=/; domain=.baidu.com  
  9. Connection: Keep-Alive  
  10.   
  11. <html><head></head></html>  
        Response类中当访问请求的文件不存在时,需要发送一段固定的响应文本给客户端,该段响应文档的格式必须严格按照http响应格式进行组织,否则客户端接收不到。具体的实现源码如下所示:

[java]  view plain copy print ?
  1. package com.wow.server;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.IOException;  
  6. import java.io.OutputStream;  
  7.   
  8. /** 
  9.  * 响应结果 
  10.  *  
  11.  * @author zhaozheng 
  12.  * 
  13.  */  
  14. public class Response {  
  15.   
  16.     private OutputStream output;  
  17.       
  18.     private  Request request;  
  19.       
  20.     private static final int BUFFER_SIZE = 1024;  
  21.       
  22.     public Response(OutputStream output) {  
  23.         this.output = output;  
  24.     }  
  25.       
  26.     public void setRequest(Request request) {  
  27.         this.request = request;  
  28.     }  
  29.       
  30.     //发送一个静态资源给客户端,若本地服务器有对应的文件则返回,否则返回404页面  
  31.     public void sendStaticResource() {  
  32.         byte[] buffer = new byte[BUFFER_SIZE];  
  33.         int ch;  
  34.         FileInputStream fis = null;  
  35.         try {  
  36.             File file = new File(HttpServer.WEB_ROOT, request.getUri());  
  37.             if(file.exists()) {  
  38.                 fis = new FileInputStream(file);  
  39.                 ch = fis.read(buffer);  
  40.                 while(ch != -1) {  
  41.                     output.write(buffer, 0, ch);  
  42.                     ch = fis.read(buffer, 0, BUFFER_SIZE);  
  43.                 }  
  44.             } else {  
  45.                 String errorMessage = "HTTP/1.1 404 File Not Found \r\n" +  
  46.                         "Content-Type: text/html\r\n" +  
  47.                         "Content-Length: 24\r\n" +  
  48.                         "\r\n" +  
  49.                         "<h1>File Not Found!</h1>";  
  50.                 output.write(errorMessage.getBytes());  
  51.             }  
  52.         } catch (Exception e) {  
  53.             System.out.println(e.toString());  
  54.         } finally {  
  55.             if(fis != null) {  
  56.                 try {  
  57.                     fis.close();  
  58.                 } catch (IOException e) {  
  59.                     e.printStackTrace();  
  60.                 }  
  61.             }  
  62.         }  
  63.     }  
  64. }  

        分析Response类,sendStaticResource方法中根据请求的uri去目录下查找是否有对应的文件存在,若有则直接读入文件并返回;否则返回一段错误消息给客户端。

当然若要运行该程序,则还需要在该工程对应的目录下创建一个webroot目录,其中可以存放一个aaa.htm文件。

1、启动该java应用程序,通过netstat可查看该程序占用了8080端口。


2、通过浏览器访问url地址:http://127.0.0.1:8080/aaa.htm


3、若访问index1.htm由于不存在则直接返回404 File Not Found信息。


4、 可以直接输入http://127.0.0.1:8080/SHUTDOWN 即可关闭服务器。
        至此一个简单的web服务器就实现完了,它本身的功能不太完整,但对于初学者了解http请求的处理、响应及web服务器的大致工作流是有帮助的。


转自:https://i-blog.csdnimg.cn/blog_migrate/7d34c866e452d227f6c4a10375a8a686.png

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值