基于Java实现简单Http服务器(转)

基于Java实现简单Http服务器(转)

本文将详细介绍如何基于java语言实现一个简单的Http服务器,文中将主要介绍三个方面的内容:1)Http协议的基本知识、2)java.net.Socket类、3)java.net.ServerSocket类,读完本文后你可以把这个服务器用多线程的技术重新编写一个更好的服务器。 
          由于Web服务器使用Http协议通信的因此也把它叫做Http服务器,Http使用可靠的TCP连接来工作,它是面向连接的通信方式,这意味着客户端和服务器每次通信都建立自己的连接,它又是无状态的连接,当数据传输完毕后客户端和服务器端的连接立刻关闭,这样可以节省服务器的资源,当然如果需要传输大量的数据,你可以在Request的头设置Connection=keep-alive使得可以复用这一个连接通道。在HTTP协议中非常重要的两个概念就是:请求(Request)和(响应)这也是我在这里要讲述的如果你想了解Http更多的内容那么请参考http://www.w3.org/Protocols/HTTP/1.1/rfc2616.pdf。

一个Http请求包括三个重要的部分: 
Method-URI-Protocol/Version 
Request headers 
Entity body 
下面是一个Http请求的例子: 
POST /servlet/default.jsp HTTP/1.1 
Accept: text/plain; text/html 
Accept-Language: en-gb 
Connection: Keep-Alive 
Host: localhost 
Referer: http://localhost/ch8/SendDetails.htm 
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) 
Content-Length: 33 
Content-Type: application/x-www-form-urlencoded 
Accept-Encoding: gzip, deflate

LastName=Franks&FirstName=Michael 
其中第一行是Method-URI-Protocol/Version ,这是非常重要的部分,你需要从中读取客户端数据传输的方式,URI以及协议和版本,在这里分别是POST / servlet/default.jsp http/1.1,我们的简单的服务器的思路就是从request中得到URI后在你的服务器上找到这个资源,比如是一个静态的html页面,然后把它发送给浏览器。记住URI是相对于你的HTTP服务器的根目录的,所以以/来开头。接下来的部分是请求头信息它们都是以name:value这样的方式构成的,这里不再多介绍了。在Header和Entity body之间有一空行叫做CRLF,这用来标记Entity body的开始的,意思是下面的是传输的数据了。

HTTP响应和请求非常相似,同样包括三个部分: 
Protocol-Status code-Description 
Response headers 
Entity body 
下面是一个具体的例子: 
HTTP/1.1 200 OK 
Server: Microsoft-IIS/4.0 
Date: Mon, 3 Jan 1998 13:13:33 GMT 
Content-Type: text/html 
Last-Modified: Mon, 11 Jan 1998 13:23:42 GMT 
Content-Length: 112

something  in html style......................

通常在J2ME联网中我们需要判断响应的状态码来决定下一步的操作,比如200代表连接成功。现在你应该清楚为什么这么做了吧。同样在Header和Entity body中有一个CRLF分割。

现在我们来看看java中的Socket类,socket其实是对编程语言的一种抽象,它提供了在网络上端对端访问的可能,但是它并不依赖于编程语言,你完全可以使用java和c语言通过socket来进行通信,在java中是通过java.net.Socket来实现的,当你要构建一个socket的时候,你只是需要调用它的构造器 
public Socket(String host,int port),其中host代表目标主机的地址或名字,port代表端口,比如80。当我们创建了一个Socket的实例后我们就可以进行通信了,如果你要基于字节来通信,那么你可以通过调用getOutputStream()和getInputStream()来得到OutputStream和InputStream的对象,如果你是基于字符通信的话那么你可以用PrintWriter和BufferedReader进行二次包装,例如PrintWriter pw = new PrintWriter(socket.getOutputStream(),true)。下面是简单的使用socket通信的代码片断,实现了向127.0.0.1:8080发送Http请求的功能

Java代码  

  1. Socket socket = new Socket("127.0.0.1", "8080");
  2. OutputStream os = socket.getOutputStream();
  3. boolean autoflush = true;
  4. PrintWriter out = new PrintWriter( socket.getOutputStream(), autoflush );
  5. BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ));
  6. // send an HTTP request to the web server
  7. out.println("GET /index.jsp HTTP/1.1");
  8. out.println("Host: localhost:8080");
  9. out.println("Connection: Close");
  10. out.println();
  11. // read the response
  12. boolean loop = true;
  13. StringBuffer sb = new StringBuffer(8096);
  14. while (loop) {
  15. if ( in.ready() ) {
  16. int i=0;
  17. while (i!=-1) {
  18. i = in.read();
  19. sb.append((char) i);
  20. }
  21. loop = false;
  22. }
  23. Thread.currentThread().sleep(50);
  24. }
  25. // display the response to the out console
  26. System.out.println(sb.toString());
  27. socket.close();

接下来介绍与Socket类对应的ServerSocket类的使用,与Socket代表客户端不同的是,ServerSocket是代表服务器端的,因为它必须在某个端口不停的监视是否有客户端连接进来。通过调用ServerSocket的构造器我们可以建立起监听特定端口的Server。例如 
new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1")); 
这样我们在本机的8080端口建立起来了ServerSocket。当你调用ServerSocket的accept()方法后,只有有连接进来的时候,这个方法才会返回一个Socket的对象,这样你就可以使用这个实例来接受或者发送数据了。记住当我们传输数据结束后要记得调用clos()方法来释放资源。

在上篇文章中我们介绍了Http协议的基本知识,以及Java中两个重要的类Socket和SocketServer类,下面我们将主要介绍如何实现一个基于java的Http服务器。 
         Http服务器主要由三个类构成:HttpServer、Request和Response。其中程序的入口在HttpServer类,它调用await()方法,使得Server开始等候客户端的连接。当客户端连接后,它将把静态的页面内容发送给客户端浏览器。下面分别介绍这三个类: 
1:HttpServer类 
     HttpServer需要有一个服务器的根目录这在WEB_ROOT变量中定义的: 
public static final String WEB_ROOT =System.getProperty("user.dir") + File.separator  + "webroot";当我们运行服务器的时候可以通过-D选项指定环境变量user.dir的值。这个类中最重要的方法就是await()方法,内容如下: 
public void await() { 
    ServerSocket serverSocket = null; 
    int port = 8080; 
    try { 
      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); 
    } 
    catch (IOException e) { 
      e.printStackTrace(); 
      System.exit(1); 
    }

// Loop waiting for a request 
    while (!shutdown) { 
      Socket socket = null; 
      InputStream input = null; 
      OutputStream output = null; 
      try { 
        socket = serverSocket.accept(); 
        input = socket.getInputStream(); 
        output = socket.getOutputStream();

// create Request object and parse 
        Request request = new Request(input); 
        request.parse();

// create Response object 
        Response response = new Response(output); 
        response.setRequest(request); 
        response.sendStaticResource();

// Close the socket 
        socket.close();

//check if the previous URI is a shutdown command 
        shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 
      } 
      catch (Exception e) { 
        e.printStackTrace(); 
        continue; 
      } 
    } 
  } 
await()方法内构造一个ServerSocket的实例,等客户端连接进来的时候把socket.getInputStream()传递给Request类进行解析,把socket.getOutputStream()传递给Response类,然后再把request对象传递给Response,最后调用Response.sendStaticResource()方法发送数据给客户端。socket.close()后监测是不是接受到了关闭Server的命令,如果是的话跳出循环结束程序。

2. Request类 
Request类的主要目的是对http请求进行封装,它有一个InputStream类型参数的构造器 
public Request(InputStream input) { 
    this.input = input; 
  } 
同时它还有一个重要的String类型的成员变量uri,Request的目的就是从inputStream中提取uri,这是由两个函数实现的 
public void parse() { 
    // Read a set of characters from the socket 
    StringBuffer request = new StringBuffer(2048); 
    int i; 
    byte[] buffer = new byte[2048]; 
    try { 
      i = input.read(buffer); 
    } 
    catch (IOException e) { 
      e.printStackTrace(); 
      i = -1; 
    } 
    for (int j=0; j<i; j++) { 
      request.append((char) buffer[j]); 
    } 
    System.out.print(request.toString()); 
    uri = parseUri(request.toString()); 
  }

private String parseUri(String requestString) { 
    int index1, index2; 
    index1 = requestString.indexOf(‘ ‘); 
    if (index1 != -1) { 
      index2 = requestString.indexOf(‘ ‘, index1 + 1); 
      if (index2 > index1) 
        return requestString.substring(index1 + 1, index2); 
    } 
    return null; 
  } 
其中parseUri(String request)方法是私有方法,它把String参数进行解析把两个空格之间的字符串返回,这是遵循Http请求的定义规则的,如果你不清楚可以参考基于java实现http服务器之一。

2.Response类 
Response的两个重要的成员变量分别是OutputStream类型的output和Requeset类型的request,这个类的功能就是从Request的实例里面得到uri,然后和WEB_ROOT进行相加得到文件所在的绝对路径,然后读取这个文件的内容把它写入到socket.getOutputStream()里面去。 
public void sendStaticResource() throws IOException { 
    byte[] bytes = new byte[BUFFER_SIZE]; 
    FileInputStream fis = null; 
    try { 
      File file = new File(HttpServer.WEB_ROOT, request.getUri()); 
      if (file.exists()) { 
        fis = new FileInputStream(file); 
        int ch = fis.read(bytes, 0, BUFFER_SIZE); 
        while (ch!=-1) { 
          output.write(bytes, 0, ch); 
          ch = fis.read(bytes, 0, BUFFER_SIZE); 
        } 
      } 
      else { 
        // file not found 
        String errorMessage = "HTTP/1.1 404 File Not Found/r/n" + 
          "Content-Type: text/html/r/n" + 
          "Content-Length: 23/r/n" + 
          "/r/n" + 
          "<h1>File Not Found</h1>"; 
        output.write(errorMessage.getBytes()); 
      } 
    } 
    catch (Exception e) { 
      // thrown if cannot instantiate a File object 
      System.out.println(e.toString() ); 
    } 
    finally { 
      if (fis!=null) 
        fis.close(); 
    } 
  }

下面说明如何运行这个应用程序,首先你从下面的地址取得源代码:http://www.onjava.com/onjava/2003/04/23/examples/HowWebServersWork.zip,把它解到你的D盘例如temp目录下,它包括三个目录src lib webroot其中webroot内有一个index.html文件供测试用。从Command中运行 
cd temp 
javac -d . src/ex01/pyrmont/*.java 
这样在当前目录(d:/temp)会出现ex01/pyrmont目录,里面是编译得到的class文件,接下来运行 
java -Duser.dir=d:/temp ex01.pyrmont.HttpServer 
然后从浏览器输入http://localhost:8080/index.html,这样你就可以看到Server发送给你的静态页面index.html了。

时间: 2024-10-17 00:27:28

基于Java实现简单Http服务器(转)的相关文章

java实现简单web服务器(分析+源代码)

在日常的开发中,我们用过很多开源的web服务器,例如tomcat.apache等等.现在我们自己实现一个简单的web服务器,基本的功能就是用户点击要访问的资源,服务器将资源发送到客户端的浏览器.为了简化操作,这里不考虑资源不存在等异常情况.web服务基于的是HTTP协议,用户在浏览器的地址栏输入要访问的地址,服务器如何得到该地址是个关键.先看下一般的HTTP请求和响应报文的一般格式: HTTP 请求报文 HTTP 响应报文 web服务器获取一个用户的连接时,会初始化一个线程和用户通信,代码如下:

基于java的简单Socket编程

1TCP协议与UDP协议     1.1 TCP               TCP是(Tranfer Control Protocol)的简称,是一种面向连接的保证可靠传输的协议.通过TCP协议传输,得到的是一个顺序的无差错的数据流.发送方和接收方的成对的两个socket之间必须建立连接,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作. TCP的

SimpleReact: 基于Java 8简单的FRP Fluent API

SimpleReact来自AOL的一个小型的支持并发的库包,其直接基于Java 8的CompletableFuture, JDK函数接口和Streams API构建,它的Fluent API能够提供解决90%的Reactive使用场景,重要的是没有带入任何复杂性.可以实现Functional Reactive Programming范式(简称:FRP,函数式响应编程) 使用SimpleReact能够方便扩展微服务,它能用于管理微服务的远程调用,能够应付高并发大型高性能平台的要求. 使用Simpl

一个基于webrick 的简单web服务器

使用ruby 自带的webrick 可以非常方便地实现一个web服务器. webrick.rb 基本代码如下: #!/usr/bin/env ruby require 'webrick' root = File.expand_path 'html' server = WEBrick::HTTPServer.new :Port => 8000, :DocumentRoot => root trap 'INT' do server.shutdown end server.start 使用命令rub

基于java语言简单版的学生信息系统

功能简介: a:可以对学生信息增删改查, b:每一种操作都是使用容器进行 c:使用自己写的工具类,可以实现多次调用,实现代码的复用,增加可读性 d:语法使用嵌套循环,一般使用while,swich..case,for 代码实现: //测试类 package com.xinboedu.www.test; public class TestSystem { public static void main(String[] args) { StudentSystem studentSystem = n

基于flask的简单web服务器 docker化

其实就是将web服务器的部署操作用docker形式来替代,那么也就是用dockerfile来实现部署操作. 下面是dockerfile FROM ubuntu MAINTAINER [email protected] RUN apt-get update RUN apt-get install -y curl git unzip python-zmq vim RUN apt-get install -y python-flask WORKDIR /work ADD app.py . CMD ["

基于Java的Http服务器几种模式演进

首先抛出问题: 程序1---错误版本 import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class HttpSimpleServer { public void startServer() throws IOException { ServerSocket s

基于Tomcat7、Java、WebSocket的服务器推送聊天室 (转)

前言 HTML5 WebSocket实现了服务器与浏览器的双向通讯,双向通讯使服务器消息推送开发更加简单,最常见的就是即时通讯和对信息实时性要求比较高的应用.以前 的服务器消息推送大部分采用的都是“轮询”和“长连接”技术,这两中技术都会对服务器产生相当大的开销,而且实时性不是特别高.WebSocket技术对 只会产生很小的开销,并且实时性特别高.下面就开始讲解如何利用WebSocket技术开发聊天室.在这个实例中,采用的是Tomcat7服务器,每个服 务器对于WebSocket的实现都是不一样的

基于java实现的简单网页日历功能,有兴趣得可以把它转换到前端实现

之前做项目的时候,因为要用到不同日期显示不同的内容,就自己做了一个日期的显示和选择功能,今天抽空把以前的代码理了一下,顺便就把之前做的日期功能给拿出来回顾一下,大家可以提点意见,帮忙完善下设计.先上一张完成后的显示效果,本人是后端程序员,对于前端的一些效果不是很熟悉,画面不好请见谅.. 下面切入正题: 我设计的日历表是11*3的显示,加上上一个月和下一个月的选项,对于大月的月份刚好足够,但小月和二月就会有空格.为了方便我在页面上展现日历,我将上一月.下一月.空字符和日期一起封装在了list集合中