看了這篇http協議,你還敢說以前會嗎?

HTTP協議簡介

超文本傳輸協議(英文:HyperText Transfer Protocol,縮寫:HTTP)是一種用於分佈式、協做式和超媒體信息系統的應用層協議。HTTP是萬維網的數據通訊的基礎。javascript

HTTP的發展是由蒂姆·伯納斯-李於1989年在歐洲核子研究組織(CERN)所發起。HTTP的標準制定由萬維網協會(World Wide Web Consortium,W3C)和互聯網工程任務組(Internet Engineering Task Force,IETF)進行協調,最終發佈了一系列的RFC,其中最著名的是1999年6月公佈的 RFC 2616,定義了HTTP協議中現今普遍使用的一個版本——HTTP 1.1。php

2014年12月,互聯網工程任務組(IETF)的Hypertext Transfer Protocol Bis(httpbis)工做小組將HTTP/2標準提議遞交至IESG進行討論,於2015年2月17日被批准。 HTTP/2標準於2015年5月以RFC 7540正式發表,取代HTTP 1.1成爲HTTP的實現標準。html

HTTP協議概述

HTTP是一個客戶端終端(用戶)和服務器端(網站)請求和應答的標準(TCP)。經過使用網頁瀏覽器、網絡爬蟲或者其它的工具,客戶端發起一個HTTP請求到服務器上指定端口(默認端口爲80)。咱們稱這個客戶端爲用戶代理程序(user agent)。應答的服務器上存儲着一些資源,好比HTML文件和圖像。咱們稱這個應答服務器爲源服務器(origin server)。在用戶代理和源服務器中間可能存在多個「中間層」,好比代理服務器、網關或者隧道(tunnel)。java

儘管TCP/IP協議是互聯網上最流行的應用,HTTP協議中,並無規定必須使用它或它支持的層。事實上,HTTP能夠在任何互聯網協議上,或其餘網絡上實現。HTTP假定其下層協議提供可靠的傳輸。所以,任何可以提供這種保證的協議均可以被其使用。所以也就是其在TCP/IP協議族使用TCP做爲其傳輸層。windows

一般,由HTTP客戶端發起一個請求,建立一個到服務器指定端口(默認是80端口)的TCP鏈接。HTTP服務器則在那個端口監聽客戶端的請求。一旦收到請求,服務器會向客戶端返回一個狀態,好比"HTTP/1.1 200 OK",以及返回的內容,如請求的文件、錯誤消息、或者其它信息。後端

HTTP工做原理

HTTP協議定義Web客戶端如何從Web服務器請求Web頁面,以及服務器如何把Web頁面傳送給客戶端。HTTP協議採用了請求/響應模型。客戶端向服務器發送一個請求報文,請求報文包含請求的方法、URL、協議版本、請求頭部和請求數據。服務器以一個狀態行做爲響應,響應的內容包括協議的版本、成功或者錯誤代碼、服務器信息、響應頭部和響應數據。瀏覽器

如下是 HTTP 請求/響應的步驟:緩存

\1. 客戶端鏈接到Web服務器
一個HTTP客戶端,一般是瀏覽器,與Web服務器的HTTP端口(默認爲80)創建一個TCP套接字鏈接。服務器

\2. 發送HTTP請求
經過TCP套接字,客戶端向Web服務器發送一個文本的請求報文,一個請求報文由請求行、請求頭部、空行和請求數據4部分組成。網絡

\3. 服務器接受請求並返回HTTP響應
Web服務器解析請求,定位請求資源。服務器將資源複本寫到TCP套接字,由客戶端讀取。一個響應由狀態行、響應頭部、空行和響應數據4部分組成。

\4. 釋放鏈接TCP鏈接
若connection 模式爲close,則服務器主動關閉TCP鏈接,客戶端被動關閉鏈接,釋放TCP鏈接;若connection 模式爲keepalive,則該鏈接會保持一段時間,在該時間內能夠繼續接收請求;

\5. 客戶端瀏覽器解析HTML內容
客戶端瀏覽器首先解析狀態行,查看代表請求是否成功的狀態代碼。而後解析每個響應頭,響應頭告知如下爲若干字節的HTML文檔和文檔的字符集。客戶端瀏覽器讀取響應數據HTML,根據HTML的語法對其進行格式化,並在瀏覽器窗口中顯示。

例如:在瀏覽器地址欄鍵入URL,按下回車以後會經歷如下流程:

  1. 瀏覽器向 DNS 服務器請求解析該 URL 中的域名所對應的 IP 地址;
  2. 解析出 IP 地址後,根據該 IP 地址和默認端口 80,和服務器創建TCP鏈接;
  3. 瀏覽器發出讀取文件(URL 中域名後面部分對應的文件)的HTTP 請求,該請求報文做爲 TCP 三次握手的第三個報文的數據發送給服務器;
  4. 服務器對瀏覽器請求做出響應,並把對應的 html 文本發送給瀏覽器;
  5. 釋放 TCP鏈接;
  6. 瀏覽器將該 html 文本並顯示內容;  

  

看了這篇http協議,你還敢說以前會嗎?

  http協議是基於TCP/IP協議之上的應用層協議。

  基於 請求-響應 的模式

    HTTP協議規定,請求從客戶端發出,最後服務器端響應該請求並 返回。換句話說,確定是先從客戶端開始創建通訊的,服務器端在沒有 接收到請求以前不會發送響應

看了這篇http協議,你還敢說以前會嗎?

  無狀態保存

    HTTP是一種不保存狀態,即無狀態(stateless)協議。HTTP協議 自身不對請求和響應之間的通訊狀態進行保存。也就是說在HTTP這個 級別,協議對於發送過的請求或響應都不作持久化處理。

看了這篇http協議,你還敢說以前會嗎?

    使用HTTP協議,每當有新的請求發送時,就會有對應的新響應產 生。協議自己並不保留以前一切的請求或響應報文的信息。這是爲了更快地處理大量事務,確保協議的可伸縮性,而特地把HTTP協議設計成 如此簡單的。但是,隨着Web的不斷髮展,因無狀態而致使業務處理變得棘手 的狀況增多了。好比,用戶登陸到一家購物網站,即便他跳轉到該站的 其餘頁面後,也須要能繼續保持登陸狀態。針對這個實例,網站爲了能 夠掌握是誰送出的請求,須要保存用戶的狀態。HTTP/1.1雖然是無狀態協議,但爲了實現指望的保持狀態功能, 因而引入了Cookie技術。有了Cookie再用HTTP協議通訊,就能夠管 理狀態了。有關Cookie的詳細內容稍後講解。

  無鏈接

    無鏈接的含義是限制每次鏈接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開鏈接。採用這種方式能夠節省傳輸時間,而且能夠提升併發性能,不能和每一個用戶創建長久的鏈接,請求一次相應一次,服務端和客戶端就中斷了。可是無鏈接有兩種方式,早期的http協議是一個請求一個響應以後,直接就斷開了,可是如今的http協議1.1版本不是直接就斷開了,而是等幾秒鐘,這幾秒鐘是等什麼呢,等着用戶有後續的操做,若是用戶在這幾秒鐘以內有新的請求,那麼仍是經過以前的鏈接通道來收發消息,若是過了這幾秒鐘用戶沒有發送新的請求,那麼就會斷開鏈接,這樣能夠提升效率,減小短期內創建鏈接的次數,由於創建鏈接也是耗時的,默認的好像是3秒中如今,可是這個時間是能夠經過我們後端的代碼來調整的,本身網站根據本身網站用戶的行爲來分析統計出一個最優的等待時間。

HTTP請求與響應

HTTP遵循請求(Request)/應答(Response)模型,Web瀏覽器向Web服務器發送請求時,Web服務器處理請求並返回適當的應答。

HTTP請求:

POST /test.php HTTP/1.1               //請求行
HOST:www.test.com                    //請求頭
User-Agent:Mozilla/5.0 (windows NT 6.1;rv:15.0)Gecko/20100101 Firefox/15.0        //空白行,表明請求頭結束
Username=admin&password=admin       //請求正文
1234

HTTP請求包括三部分,分別是請求行(請求方法)、請求頭(消息報頭)和請求正文。

HTTP請求第一行爲請求行,由三部分組成,第一部分說明了該請求時POST請求,第二部分是一個斜槓(/login.php),用來講明請求是該域名根目錄下的login.php,第三部分說明使用的是HTTP1.1版本。

HTTP請求第二行至空白行爲請求頭(也被稱爲消息頭)。其中,HOST表明請求主機地址,User-Agent表明瀏覽器的標識,請求頭由客戶端自行設定。

HTTP請求第三行爲請求正文,請求正文是可選的,它最常出如今POST請求方式中。

HTTP請求方法:

根據 HTTP 標準,HTTP 請求可使用多種請求方法。

HTTP1.0 定義了三種請求方法: GET, POST 和 HEAD方法。

HTTP1.1 新增了五種請求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。

序號 方法 描述
1 GET 請求指定的頁面信息,並返回實體主體。
2 HEAD 相似於 GET 請求,只不過返回的響應中沒有具體的內容,用於獲取報頭
3 POST 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST 請求可能會致使新的資源的創建和/或已有資源的修改。
4 PUT 從客戶端向服務器傳送的數據取代指定的文檔的內容。
5 DELETE 請求服務器刪除指定的頁面。
6 CONNECT HTTP/1.1 協議中預留給可以將鏈接改成管道方式的代理服務器。
7 OPTIONS 容許客戶端查看服務器的性能。
8 TRACE 回顯服務器收到的請求,主要用於測試或診斷。
8 PATCH 是對 PUT 方法的補充,用來對已知資源進行局部更新 。

GET、POST、HEAD、PUT請求:

  • GET:GET方法用於獲取請求頁面的指定信息。若是請求資源爲動態腳本(非HTML),那麼返回文本是Web容器解析後的HTML源代碼。GET請求沒有消息主體,所以在消息頭後的空白行是沒有其餘數據。
  • POST:POST方法也與GET方法類似,但最大的區別在於,GET方法沒有請求內容,而POST是有請求內容的。
  • HEAD:這個請求的功能與GET請求類似,不一樣之處在於服務器不會再其響應中返回消息主體,所以,這種方法可用於檢查某一資源在向其提交GET請求前是否存在。
  • PUT:PUT方法用於請求服務器把請求中的實體存儲在請求資源下,若是請求資源已經在服務器中存在,那麼將會用此請求中的數據替換原先的數據。向服務器上傳指定的資源。

HTTP響應:

HTTP/1.1 200 OK                      //響應行
Date: Sun, 15 Nov 2015 11:02:04 GMT    //響應頭
Server: bfe/1.0.8.9
Content-Length: 2605
Content-Type: application/javascript
Cache-Control: max-age=315360000
Expires: Fri, 13 Jun 2025 09:54:00 GMT
Content-Encoding: gzip
Set-Cookie: H_PS_PSSID=2022_1438_1944_1788; path=/; domain=test.com
Connection: keep-alive
                          //空白行,表明響應頭結束
<html>
<head><title> Index.html </title></head>  //響應正文消息主題

1234567891011121314

HTTP響應的第一行爲響應行,其中有HTTP版本(HTTP/1.1)、狀態碼(200)以及消息「OK」。

第二行至末尾的空白行爲響應頭,由服務器向客戶端發送。

消息頭以後是響應正文,是服務器向客戶端發送的HTML數據。

HTTP消息頭:

請求頭:請求頭只出如今HTTP請求中,請求報頭容許客戶端向服務端傳遞請求的附加信息和客戶端自身信息。

響應頭:響應頭是服務器根據請求向客戶端發送的HTTP頭。

HTTP請求頭:

  • Host 請求報頭域主要用於指定被請求資源的Internet主機和端口。
  • User-Agent 請求報頭域容許客戶端將它的操做系統、瀏覽器和其餘屬性告訴服務器。
  • Referer 包含一個URL,表明當前訪問URL的上一個URL,也就是說,用戶是從什麼地方來到本頁面。當前請求的原始URL地址。
  • Cookie 是很是重要的請求頭,經常使用來表示請求者的身份等。
  • Accept 這個消息頭用於告訴服務器客戶端願意接受那些內容,好比圖像類,辦公文檔格式等等。

HTTP響應頭信息:

應答頭 說明
Allow 服務器支持哪些請求方法(如GET、POST等)。
Content-Encoding 文檔的編碼(Encode)方法。只有在解碼以後才能夠獲得Content-Type頭指定的內容類型。利用gzip壓縮文檔可以顯著地減小HTML文檔的下載時間。
Content-Length 表示內容長度。
Content-Type 表示後面的文檔屬於什麼MIME類型。Servlet默認爲text/plain,但一般須要顯式地指定爲text/html。
Date 當前的GMT時間。
Expires 應該在何時認爲文檔已通過期,從而再也不緩存它?
Last-Modified 文檔的最後改動時間。客戶能夠經過If-Modified-Since請求頭提供一個日期,該請求將被視爲一個條件GET,只有改動時間遲於指定時間的文檔纔會返回,不然返回一個304(Not Modified)狀態。
Location 表示客戶應當到哪裏去提取文檔。Location一般不是直接設置的,而是經過HttpServletResponse的sendRedirect方法,該方法同時設置狀態代碼爲302。
Refresh 表示瀏覽器應該在多少時間以後刷新文檔,以秒計。注意Refresh頭不屬於HTTP 1.1正式規範的一部分,而是一個擴展,但Netscape和IE都支持它。
Server 服務器名字。Servlet通常不設置這個值,而是由Web服務器本身設置。
Set-Cookie 設置和頁面關聯的Cookie。
WWW-Authenticate 客戶應該在Authorization頭中提供什麼類型的受權信息?在包含401(Unauthorized)狀態行的應答中這個頭是必需的。

攔截HTTP請求的分析點:

看了這篇http協議,你還敢說以前會嗎?

HTTP狀態與會話

HTTP狀態碼:

當瀏覽者訪問一個網頁時,瀏覽者的瀏覽器會向網頁所在服務器發出請求。當瀏覽器接收並顯示網頁前,此網頁所在的服務器會返回一個包含HTTP狀態碼的信息頭(server header)用以響應瀏覽器的請求。

HTTP狀態碼的英文爲HTTP Status Code。

五種狀態碼:

  • 1xx:信息提示,表示請求已被成功接收,繼續處理。
  • 2xx:請求被成功提交。
  • 3xx:客戶端被重定向到其餘資源。
  • 4xx:客戶端錯誤狀態碼,格式錯誤或者不存在資源。
  • 5xx:描述服務器內部錯誤。

常見的狀態碼描述以下:

  • 200:客戶端請求成功,是最多見的狀態。
  • 302:重定向。
  • 404:請求資源不存在,是最多見的狀態。
  • 400:客戶端請求有語法錯誤,不能被服務器所理解。
  • 401:請求未經受權。
  • 403:服務器收到請求,可是拒絕提供服務。
  • 500:服務器內部錯誤,是最多見的狀態。
  • 503:服務器當前不能處理客戶端的請求。

會話與會話狀態簡介:

WEB應用中的會話是指一個客戶端瀏覽器與WEB服務器之間連續發生的一系列請求和響應過程。

WEB應用的會話狀態是指WEB服務器與瀏覽器在會話過程當中產生的狀態信息,藉助會話狀態,WEB服務器可以把屬於同一會話中的一系列的請求和響應過程關聯起來。

如何實現有狀態的會話:

某個用戶從網站的登陸頁面登入後,在進入購物頁面購物時,負責處理購物請求的服務器程序必須知道處理上一次請求的程序所獲得的用戶信息。

HTTP協議是一種無狀態的協議,WEB服務器自己不能識別出哪些請求是同一個瀏覽器發出的 ,瀏覽器的每一次請求都是徹底孤立的。

WEB服務器端程序要能從大量的請求消息中區分出哪些請求消息屬於同一個會話,即能識別出來自同一個瀏覽器的訪問請求,這須要瀏覽器對其發出的每一個請求消息都進行標識,屬於同一個會話中的請求消息都附帶一樣的標識號,而屬於不一樣會話的請求消息老是附帶不一樣的標識號,這個標識號就稱之爲會話ID(SessionID)

會話ID能夠經過一種稱之爲Cookie的技術在請求消息中進行傳遞,也能夠做爲請求URL的附加參數進行傳遞會話ID是WEB服務器爲每客戶端瀏覽器分配的一個惟一代號,它一般是在WEB服務器接收到某個瀏覽器的第一次訪問時產生,而且隨同響應消息一道發送給瀏覽器。

會話過程由WEB服務器端的程序開啓,一旦開啓了一個會話,服務器端程序就要爲這個會話建立一個獨立的存儲結構來保存該會話的狀態信息,同一個會話中的訪問請求均可以且只能訪問屬於該會話的存儲結構中的狀態信息。

Java 實現HTTP協議

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

public class HttpServer {

    public static void main(String[] args) throws Exception {
        // 建立ServerSocketChannel,監聽8080端口
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(8080));
        // 設置爲非阻塞模式
        ssc.configureBlocking(false);
        // 爲ssc註冊選擇器
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        // 建立處理器
        while (true) {
            // 等待請求,每次等待阻塞3s,超過3s後線程繼續向下運行,若是傳入0或者不傳參數將一直阻塞
            if (selector.select(3000) == 0) {
                continue;
            }
            // 獲取待處理的SelectionKey
            Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();

            while (keyIter.hasNext()) {
                SelectionKey key = keyIter.next();
                // 啓動新線程處理SelectionKey
                new Thread(new HttpHandler(key)).run();
                // 處理完後,從待處理的SelectionKey迭代器中移除當前所使用的key
                keyIter.remove();
            }
        }
    }

    private static class HttpHandler implements Runnable {
        private int bufferSize = 1024;
        private String localCharset = "UTF-8";
        private SelectionKey key;

        public HttpHandler(SelectionKey key) {
            this.key = key;
        }

        public void handleAccept() throws IOException {
            SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
            clientChannel.configureBlocking(false);
            clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
        }

        public void handleRead() throws IOException {
            // 獲取channel
            SocketChannel sc = (SocketChannel) key.channel();
            // 獲取buffer並重置
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            buffer.clear();
            // 沒有讀到內容則關閉
            if (sc.read(buffer) == -1) {
                sc.close();
            } else {
                // 接收請求數據
                buffer.flip();
                String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();

                // 控制檯打印請求報文頭
                String[] requestMessage = receivedString.split("\r\n");
                for (String s : requestMessage) {
                    System.out.println(s);
                    // 遇到空行說明報文頭已經打印完
                    if (s.isEmpty()) {
                        break;
                    }
                }

                // 控制檯打印首行信息
                String[] firstLine = requestMessage[0].split(" ");
                System.out.println();
                System.out.println("Method:\t" + firstLine[0]);
                System.out.println("url:\t" + firstLine[1]);
                System.out.println("HTTP Version:\t" + firstLine[2]);
                System.out.println();

                // 返回客戶端
                StringBuilder sendString = new StringBuilder();
                sendString.append("HTTP/1.1 200 OK\r\n");//響應報文首行,200表示處理成功
                sendString.append("Content-Type:text/html;charset=" + localCharset + "\r\n");
                sendString.append("\r\n");// 報文頭結束後加一個空行

                sendString.append("<!DOCTYPE html>");
                sendString.append("<html lang=\"en\">");
                sendString.append("<head>");
                sendString.append("    <meta charset=\"UTF-8\">");
                sendString.append("    <title>Title</title>");
                sendString.append("</head>");
                sendString.append("<body>");
                sendString.append("    <h4>hello world! </h4>");
                sendString.append("</body>");
                sendString.append("</html>");

                buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));
                sc.write(buffer);
                sc.close();
            }
        }

        @Override
        public void run() {
            try {
                // 接收到鏈接請求時
                if (key.isAcceptable()) {
                    handleAccept();
                }
                // 讀數據
                if (key.isReadable()) {
                    handleRead();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}
相關文章
相關標籤/搜索