一个简单web服务器的实现

Posted zhaozheng7758

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个简单web服务器的实现相关的知识,希望对你有一定的参考价值。

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


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

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

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

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

package com.wow.server;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 一个简单的web应用服务器
 * @author zhaozheng
 *
 */
public class HttpServer 

	public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
	private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
	private boolean shutdown = false;
	
	public static void main(String[] args) 
		HttpServer server = new HttpServer();
		server.start();
	
	
	//启动服务器,并接收用户请求进行处理
	public void  start() 
		ServerSocket serverSocket = null;
		int  PORT = 8080;
	    try 
			serverSocket = new ServerSocket(PORT, 1, InetAddress.getByName("127.0.0.1"));
		 catch (UnknownHostException e) 
			e.printStackTrace();
			System.exit(-1);
		 catch (IOException e) 
			e.printStackTrace();
			System.exit(-1);
		
		
		//若请求的命令不为SHUTDOWN时,循环处理请求
		while(!shutdown) 
			Socket socket = null;
			InputStream input = null;
			OutputStream output = null;
			
			try 
				//创建socket进行请求处理
				socket = serverSocket.accept();
				input = socket.getInputStream();
				output = socket.getOutputStream();
				//接收请求
				Request request = new Request(input);
				request.parser();
				//处理请求并返回结果
				Response response = new Response(output);
				response.setRequest(request);
				response.sendStaticResource();
				//关闭socket
				socket.close();
				//若请求命令为关闭,则关闭服务器
				shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
			 catch (IOException e) 
				e.printStackTrace();
				continue;
			
		
		
	

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

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

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

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

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

package com.wow.server;

import java.io.IOException;
import java.io.InputStream;

/**
   *接收到的请求串的具体格式如下:
   * GET /aaa.htm HTTP/1.1
   * Host: 127.0.0.1:8080
   * Connection: keep-alive
   * User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11
   * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,q=0.8
   * Accept-Encoding: gzip,deflate,sdch
   * Accept-Language: zh-CN,zh;q=0.8
   * Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3
   * 
   * @author zhaozheng
   *
 */
public class Request 

	private InputStream input;
	private String uri;
	
	public Request(InputStream input) 
		this.input = input;
	
	
	public void parser() 
		StringBuffer request = new StringBuffer();
		byte[] buffer = new byte[2048];
		int i = 0;
		
		try 
			i = input.read(buffer);
		 catch (IOException e) 
			e.printStackTrace();
			i = -1;
		
		
		for(int k = 0; k < i; k++) 
			request.append((char)buffer[k]);
		
		
		uri = parserUri(request.toString());
		
	
	
	private String parserUri(String requestData) 
		int index1, index2;
		index1 = requestData.indexOf(' ');
		if(index1 != -1) 
			index2 = requestData.indexOf(' ', index1 + 1);
			if(index2 > index1) 
				return requestData.substring(index1 + 1, index2);
			
		
		
		return null;
	
	
	public String getUri() 
		return uri;
	

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

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

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

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

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

HTTP/1.1 200 OK 
Date: Mon, 07 Jan 2013 14:31:36 GMT 
Server: BWS/1.0 
Content-Length: 4029 
Content-Type: text/html;charset=gbk
Cache-Control: private Expires: Mon, 07 Jan 2013 14:31:36 GMT 
Content-Encoding: gzip 
Set-Cookie: H_PS_PSSID=1445_1661; path=/; domain=.baidu.com
Connection: Keep-Alive

<html><head>…</head></html>
        Response类中当访问请求的文件不存在时,需要发送一段固定的响应文本给客户端,该段响应文档的格式必须严格按照http响应格式进行组织,否则客户端接收不到。具体的实现源码如下所示:

package com.wow.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * 响应结果
 * 
 * @author zhaozheng
 *
 */
public class Response 

	private OutputStream output;
	
	private  Request request;
	
	private static final int BUFFER_SIZE = 1024;
	
	public Response(OutputStream output) 
		this.output = output;
	
	
	public void setRequest(Request request) 
		this.request = request;
	
	
	//发送一个静态资源给客户端,若本地服务器有对应的文件则返回,否则返回404页面
	public void sendStaticResource() 
		byte[] buffer = new byte[BUFFER_SIZE];
		int ch;
		FileInputStream fis = null;
		try 
			File file = new File(HttpServer.WEB_ROOT, request.getUri());
			if(file.exists()) 
				fis = new FileInputStream(file);
				ch = fis.read(buffer);
				while(ch != -1) 
					output.write(buffer, 0, ch);
					ch = fis.read(buffer, 0, BUFFER_SIZE);
				
			 else 
				String errorMessage = "HTTP/1.1 404 File Not Found \\r\\n" +
						"Content-Type: text/html\\r\\n" +
						"Content-Length: 24\\r\\n" +
						"\\r\\n" +
						"<h1>File Not Found!</h1>";
				output.write(errorMessage.getBytes());
			
		 catch (Exception e) 
			System.out.println(e.toString());
		 finally 
			if(fis != null) 
				try 
					fis.close();
				 catch (IOException e) 
					e.printStackTrace();
				
			
		
	

        分析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服务器的大致工作流是有帮助的。

以上是关于一个简单web服务器的实现的主要内容,如果未能解决你的问题,请参考以下文章

WebSocket 简单实现

Nginx实现简单动静分离

Nginx实现简单动静分离

Apollo服务端设计原理剖析

C#中使用Socket实现简单Web服务器

Nancy之结合tinyfox给我们的应用提供简单的数据服务