HTTP网络协议与手写Web服务容器

Http协议

1.深入概念
  Http:HyperText Transfer Protocol,即是超文本传输协议。

2.浅出概念(使用浏览器访问服务器端网页时需要遵循的一系列规则)
  Http:将各种不同浏览器或各种自研客户端的文字信息组织在一起的网状文本数据。
  协议:多方一起约定的一系列规则,然后大家活动必须遵循这些规则,就像法律大家必须遵循才能享受法律提供的保护。

  举个栗子:我们平时去饭店吃饭通常会有以下几个步骤:
  1.坐到合适的座位上。
  2.服务员拿出菜单点餐。
  3.服务员按照点餐菜单下单。
  4.服务员上菜。
  5.吃饭。
  6.买单走人。

  这是我们吃饭通常的步骤,如果我把这些步骤和饭店约定好,每次到了这家饭店就按这个流程来执行,
  我要遵循这个步骤来吃饭,同样饭店也要遵循这个步骤提供饭菜,那么我和店家的协议就达成了,从而可以愉快的吃饭。

  例如:我每天中午都会去这家饭店吃一份“蛋炒饭”,并按照上边的步骤,且与商家协定好,商家也答应了,就成了所谓的“我和饭店的协议”。
  那么到了12点我过去,商家就会主动提供一份“蛋炒饭”并且是加料的特殊待遇,那么这个就是“我和店家提供午餐的协议”的体现。而其他人12点过去
  是没有现成蛋炒饭和加料服务的,因为他们没有达成协议。

3.Http原理

  在网络上经常会遇见 WWW 服务应用系统,该应用系统最基本的传输单位是 Web 网页。WWW 服务应用系统的工作方式是基于 B/S 模式,
  由 Web 浏览器即B和 Web服务器(服务器)即S来构成,两者之间采用超文本传送协议(HTTP)进行通信。
  HTTP 协议是基于 TCP/IP 协议之上的协议,为浏览器和 Web 服务器之间的应用层协议,是一种通用的、无状态的和面向对象的协议。
  当通过 HTTP 协议连接服务器时,一般会经历以下 4 个步骤。

(1)连接:Web 浏览器与 Web 服务器建立连接,打开一个名为 socket(套接字)的虚拟文件,此文件的建立标志着连接建立成功。

(2)请求:Web 浏览器通过 Socket 向 Web 服务器提交请求。HTTP 的请求一般是 GET或 POST 命令(POST 用于 FORM 参数的传递)。

(3)应答:Web 浏览器提交请求后,通过 HTTP 协议传送给 Web 服务器。Web 服务器接到后,进行事务处理,处理结果又通过 HTTP 传回给 Web 浏览器,从而在 Web 浏览器上显示出所请求的页面。

(4)关闭连接:当应答结束后,Web 浏览器与 Web 服务器必须断开,以保证其他 Web浏览器能够与 Web 服务器建立连接。


  例如当 IE 浏览器与 www.test.com:8080/mydir/index.html 建立了连接,
  就会发送 GET命令 GET /mydir/index.html HTTP/1.0。主机名为 www.test.com 的 Web 服务器就会从它的文档空间中搜索子目录 mydir 的文件 index.html。
  如果找到该文件,Web 服务器把该文件内容传送给相应的 Web 浏览器。
  为了能够让浏览器知道传送回文件的类型,Web 服务器首先传送一些 HTTP 头信息,然后传送具体内容(即 HTTP 体信息),HTTP 头信息和 HTTP 体信息之间用一个空行分开。

  常用的 HTTP 头信息如下。

(1)HTTP 1.0 200 OK:这是 Web 服务器应答的第一行,列出服务器正在运行的 HTTP版本号和应答代码。代码“200 OK”表示请求完成。

(2)MIME_Version:1.0:表示 MIME 类型的版本。

(3)content_type:这个头信息非常重要,表示 HTTP 体信息的 MIME 类型。如content_type:text/html 表示传送的数据是 HTML 文档。

(4)content_length:长度值,表示 HTTP 体信息的长度(字节)。

4.客户端和服务端详解

  1)客户端
  客户端主要职能有两个,一个向服务器发送请求,另一个是接收服务器返回的报文并解释成友善的信息供我们阅读。
  客户端大概有以下几类:浏览器、我们自己写的应用程序(桌面应用和APP应用)、其他。我们在日常生活中使用最多的就是浏览器了。

  下面我们以IE为示例:我们在地址栏输入网址并巧下回车,浏览器会为我们做如下的处理:

  1解析出协议(http)、域名(www.baidu.com)及访问资源(index.html)
  2使用http协议并创建请求报文向服务器端发送请求
  3接收到服务器返回的内容并渲染展示。

  大部份客户端的工作都由客户端软件进行了包装和处理。但出于学习的目的我们使用最原始的命令telnet来模拟http请求。telnet服务需要在控制面板打开
  点击【开始】--【运行】--输入cmd打开命令行窗口。在命令行窗口中输入telnet www.baidu.com 80然后敲击一下回车
  使用快捷键"Ctrl+]"(ctrl+右中括号)来打开本地回显功能,这样我们可以看到输入到窗口的内容,否则输入的内容不显示。
  再次敲击一下回车。
  此时出现空白窗口,我们就可以模拟http请求向服务器发送请求报文了。在窗口中输入:
  GET /index.html HTTP/1.1
  Host:www.baidu.com
  注意此处敲击两下回车。成功后服务器返回的报文会显示在命令行窗口

  小结:客户端主要处理发送请求及处理服务器返回的报文。


  2)传输内容
  (1)URL:URL是寻找信息时所需要的资源位置。通过URL客户端才能找到网络中的大量数据资源 。如:http://www.baidu.com:80/index.html
  URL分为三个部分:
  第一部分http是URL的方案,方案告诉客户端使用什么样的协议去访问服务器了,也可以是fpt或https等。
  第二部分www.baidu.com:80,指服务器的位置。
  第三部分 /index.html是资源路径,说明了请求的是服务器上哪个特定的本地资源。

  URL语法:<协议>://<用户名>:<密码>@主机:端口/路径;参数?查询
  几乎没有几个URL包含了所有这些组件。
  协议:访问服务器时使用的协议类型(FTP,HTTP,HTTPS)
  用户名:有些协议需要使用用户名,如FTP匿名用户登录:ftp://anonymous@cncoder.me
  密码:有些协议需要使用密码,如FTP用户名密码登录:ftp://username:password@cncoder.me
  主机:资源服务器的域名或IP地址
  端口:服务器监听的端口号,http协议默认是80端口。
  路径:服务器上本地资源的路径。用“/”来分格。如:/theme/image/logo.png
  参数:某些协议会输入指定的参数,参数以name:value形式出现,中间用“;”来分格。
  查询:用“?”与其他的组件分格开,并用“&”来分隔查询。如:?id=1&name=cncoder

  (2)报文:HTTP报文可以分为两类:请求报文和响应报文,请求报文会向服务器请求一个动作,响应报文会将请求的结果返回给客户端。请求报文和响应报文的结构基本相同。
  下面为我们获取服务器上一张图片的报文。
  请求报文:
  GET /Theme/Image/logo.png HTTP/1.1
  Host:www.cncoder.me
  响应报文:
  HTTP/1.1 200 OK
  Content-Type:image/gif
  Conetnt-Length4567

  请求报文的格式:
  <方法><资源路径><协议版本>
  <报文头信息>
  <报文体信息>

  响应报文格式:
  <协议版本><状态码><原因短语>
  <报文头信息>
  <报文体信息>

  请求报文和响应报文只有起始行的语法不同。


  常见请求报文头信息
  Host:” www.cncoder.me”
  User-Agent:"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"
  Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
  Accept-Language:"zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"
  Accept-Encoding:"gzip, deflate"
  Connection:"keep-alive"

  常见响应头信息
  Content-Encoding:"gzip"
  Content-Length:"1779"
  Content-Type:"text/html"
  Date:"Sun, 21 Aug 2016 11:45:07 GMT"
  Last-Modified:"Sun, 21 Aug 2016 11:33:32 GMT"
  Server:"Microsoft-IIS/7.5"
  X-Powered-By:"ASP.NET"


  (2.1)方法:请求的起始行以方法作为起始,方法用来告诉服务器要如何做。如:GET /index.html HTTP/1.1 中的GET就是方法
  1.GET 请求指定的页面信息,并返回实体主体。
  2.HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
  3.POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
  4.PUT 从客户端向服务器传送的数据取代指定的文档的内容。
  5.DELETE 请求服务器删除指定的页面。
  6.CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
  7.OPTIONS 允许客户端查看服务器的性能。
  8.TRACE 回显服务器收到的请求,主要用于测试或诊断。

  在开发过程中我们最常用的就是GET和POST方法。


  (2.2)状态码:当客户端发起一次http请请求后,服务器会返回一个包含HTTP状态码的信息头(server header)用以响应客户端的请求。HTTP状态码的英文为HTTP Status Code。
  1.100~199 信息,服务器收到请求,需要请求者继续执行操作
  2.200~299 成功,操作被成功接收并处理
  3.300~399 重定向,需要进一步的操作以完成请求
  4.400~499 客户端错误,请求包含语法错误或无法完成请求
  5.500~599 服务器错误,服务器在处理请求的过程中发生了错误

  常用状态码如下:
  200-请求成功

  403(禁止)服务器拒绝请求。
  404-请求的资源不存在
  500-内部服务器错误

  502(错误网关)服务器作为网关或代理,从上游服务器收到无效响应。

  503(服务不可用)服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。


3)服务器

  (0)    服务器软件一直在监听端口是否有新的请求到达,如iis或tomcat在建立web站点后,默认会一直监听80端口等待http请求到过服务器。
  (1)    建立连接:如果客户端已经打开一条到服务器的持久连接,则可以直接使用,否则,客户端需要在服务器打开一条新的连接。
  (2)    接收请求报文:连接上有数据到过时,Web服务器会从网络连接中读取数据,并将请求报文中的内容解析出来。


  如请求报文
  GET /index.html HTTP/1.1CRLF
  Host:www.cncoder.me
  Accept:text/htmlCRLF


  接收后会被表示为
  Name:Method  Value:GET
  Name:Uri  Value:index.html
  Name:Version  1.1
  Name:Host  Value:www.cncoder.me
  Name:Accept  Value:test/html
  (3)    处理请求:当请求被接收和表示后,服务器便可以根据请求报文进行处理了。如:POST方法中提出报文主体的数据并插入到数据库中。
  (4)    访问资源:请求处理完成后,比如Web会根据数据生成一系列的HTML页面或图片等信息。此步骤将访问这些存储在服务器上的物理文件。
  (5)    构建响应:Web服务器在识别资源后,构造响应报文。响应报文中包含状态码、首行、主体等内容。
  (6)    发送响应:服务器将响应的数据发送给客户端机器。
  (7)    记录日志:请求结束会Web服务器会在日志文件中添加一条请求记录。

  (8)手写WEB服务器

 

  (8.1)实现HTTP协议原理WEB服务器

  当创建实现 HTTP 协议的 Web 服务器时,一般都会根据 HTTP 协议的工作原理,经历固定的步骤。具体步骤如下。
  (1)创建 ServerSocket 类对象,监听端口 8080。这是为了区别于 HTTP 的标准 TCP/IP端口 80 而取的。
  (2)等待、接受客户机连接到端口 8080,得到与客户机连接的 socket。
  (3)创建与 socket 套接字相关联的输入流 instream 和输出流 outstream。
  (4)从与 socket 关联的输入流 instream 中读取一行客户机提交的请求信息,请求信息的格式为 GET 路径/文件名 HTTP/1.0。
  (5)从请求信息中获取请求类型。如果请求类型是 GET,则从请求信息中获取所访问的 HTML 文件名。没有 HTML 文件名时,则以 index.html 作为文件名。
  (6)如果 HTML 文件存在,则打开 HTML 文件,把 HTTP 头信息和 HTML 文件内容通过 Socket 传回给 Web 浏览器,然后关闭文件;否则发送错误信息给 Web 浏览器
  (7)关闭与相应 Web 浏览器连接的 socket 套接字。

  8.2构建步骤

  首先创建了一个实现与浏览器通信的工具类,然后再创建了调用工具类的 Web 服务器类,最后为了测试 Web 服务器的功能,创建了一个简单的网页。

  8.3使用JAVA实现

   创建CommunicateThread.java 类用来实现与浏览器的通信功能

public class CommunicateThread extends Thread {
    
    Socket client; // 创建连接 Web 浏览器的 Socket 类对象
    int counter; // 创建计数器对象

    public CommunicateThread(Socket cl, int c) {
        client = cl;
        counter = c;
    }
    //实现 run()方法
    public void run() {
    try {
            //获取客户端的 IP 地址变量
            String destIP = client.getInetAddress().toString();
            //获取客户机端口号
            int destport = client.getPort(); 
            //创建 PrintStream 输出流对象
            PrintStream outstream = new PrintStream(client.getOutputStream());
            //创建输入流的过滤对象
            DataInputStream instream = new DataInputStream(client.getInputStream());
            //读取 Web 浏览器提交的请求信息
            String inline = instream.readLine();
            System.out.println("Received:" + inline);
            //判断请求信息
            //当为 GET 请求时
            if (getrequest(inline)) { 
            String filename = getfilename(inline); //获取文件的名字
            File file = new File(filename); //创建网页的 file 对象
            if (file.exists()) { //当文件存在,则回送给浏览器
            //设置返回信息
            System.out.println(filename + " requested.");
            outstream.println("HTTP/1.0 200 OK");
            outstream.println("MIME_version:1.0");
            outstream.println("Content_Type:text/html");
            int len = (int) file.length();
            outstream.println("Content_Length:" + len);
            outstream.println("");
            sendfile(outstream, file); //调用 sendfile()方法发送文件
            outstream.flush();
            } else { //当文件不存在时
            //创建显示的内容
            String notfound = "<html><head><title>Not Found</title></head><body><h1>Error 404-file not found</h1></body></html>";
            //设置返回的信息
            outstream.println("HTTP/1.0 404 no found");
            outstream.println("Content_Type:text/html");
            outstream.println("Content_Length:"+notfound.length()+2);
            outstream.println("");
            outstream.println(notfound);
            outstream.flush();
            }
            }
            long time = 1;
            while (time < 11100000) {
            time++;
            } //延时
            client.close();
            } catch (IOException e) {
            System.out.println("Exception:" + e);
            }
            }

    boolean getrequest(String s) { // 判断请求的类型是否为 GET
        if (s.length() > 0) {
            if (s.substring(0, 3).equalsIgnoreCase("GET"))
                return true;
        }
        return false;
    }

    String getfilename(String s) { // 实现文件名的获取
        String f = s.substring(s.indexOf(' ') + 1);
        f = f.substring(0, f.indexOf(' '));
        try {
            if (f.charAt(0) == '/')
                f = f.substring(1);
        } catch (StringIndexOutOfBoundsException e) {
            System.out.println("Exception:" + e);
        }
        if (f.equals(""))
            f = "index.html";
        return f;
    }

    void sendfile(PrintStream outs, File file) { // 实现发送文件到浏览器的功能
        try {
            // 对象 file 的输入流对象
            DataInputStreamin = newDataInputStream(newFileInputStream(file));
            int len = (int) file.length(); // 获取文件的长度
            byte buf[] = new byte[len]; // 创建字节数组
            in.readFully(buf); // 读取内容存储到数组 buf 中
            outs.write(buf, 0, len); // 输出数组 buf 中的内容
            // 输入流和输出流
            outs.flush();
            in.close();
        } catch (Exception e) {
            System.out.println("Error retrieving file.");
            System.exit(1);
        }
    }
}

  代码解析

  为了便于实现相应功能,代码中创建了 3 个方法
  getrequest()方法用来检测客户的请求是否为 GET
  getfilename(s)方法是从客户请求信息 s 中获取要访问的 HTML文件名
  sendfile()方法把指定文件内容通过 Socket 传回给 Web 浏览器
  线程子类主要用来实现两个功能,即分析 Web 浏览器提交的请求及把应答信息传回给 Web 浏览器

 

  创建实现 Web 服务器的类WebServer.java 类用来实现 Web 服务器,该类主要通过调用 CommunicateThread 类

public class WebServer {
    public static void main(String args[]) {
        // 创建两个端口号和连接次数的变量
        int i = 1, PORT = 8080;
        ServerSocket server = null; // 创建 ServerSocket 对象
        Socket client = null; // 创建 Socket 对象
        try {
            server = new ServerSocket(PORT); // 为对象 ServerSocket 赋值
            // 输出相应的信息
            System.out.println("Web Server is listening on port "+ server.getLocalPort());
            for (;;) { // 通过无限循环来不断的接收监听
                // 获取客户机的连接请求
                client = server.accept(); 
                // 创建 CommunicateThread 对象并启动
                new CommunicateThread(client, i).start();
                i++; // 实现变量 i 的自增
            }
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

  在上述代码中首先创建 ServerSocket 对象,然后通过该对象的 accept()方法不停地接收请求,如果连接成功,则调用 CommunicateThread 类的 start()方法来处理请求。

 

  创建浏览器请求页面

  当浏览器发送请求到 Web 服务器时,实际情况是 Web 服务器会根据浏览器的请求返
  回相应的内容,本项目只是简单地返回固定的名为 index.html 的网页

<HTML>
    <HEAD>
    <META HTTP-EQUIV="Content-Type" content="text/html; charset=utf-8">
    <TITLE>Java Web 服务器</TITLE>
    </HEAD>
    <BODY>
    <!--设置主题-->
    <h3>
    WEB 服务器主页
    </h3>
    <hr> <!--设置横线-->
    </BODY>
</HTML>

6.扩展内容引导
1.    Web的组件结构中还包含代理、缓存、网关、隧道。
2.    HTTP协议是建立在TCP/IP协议基础之上。
3.    HTTP性能很大一方面在于建立连接。HTTP/1.0时使用Keep-Alive。HTTP/1.1使用管道连接。

原文地址:https://www.cnblogs.com/zhuoqingsen/p/7993663.html