HTTP编程

简介:

一、HTTP概述:

    Ø web工作方式:
    我们平时浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后就会显示出你想要浏览的内容。在这个看似简单的用户行为背后,到底隐藏了些什么呢?
    对于普通的上网过程,系统其实是这样做的:
    浏览器本身是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接,等浏览器发送完HTTP Request(请求)包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回HTTP Response(响应)包;客户端收到来自服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。
    

    一个Web服务器也被称为HTTP服务器,它通过HTTP协议与客户端通信。这个客户端通常指的是Web浏览器(其实手机端客户端内部也是浏览器实现的)。 
    web服务器的工作原理可以简单地归纳为:
        1) 客户机通过TCP/IP协议建立到服务器的TCP连接;
        2) 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档;
        3) 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端;
        4) 客户机与服务器断开,由客户端解释HTML文档,在客户端屏幕上渲染图形结果。

    Ø HTTP协议:
    超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,它详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。如下图所示:
    
    注:
    SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。
    Ø 地址(URL):
    URL全称为Unique Resource Location,即统一资源定位符,用来表示网络资源,可以理解为网络文件路径。URL的格式如下:
        http://host[":"port][abs_path]
        http://192.168.31.1/html/index
    URL的长度有限制,不同的服务器的限制值不太相同,但是不能无限长。

二、HTTP报文解析:

    Ø 请求包和响应包介绍:
    
    Ø 请求报文分析:
        1) 想要知道请求报文格式并对其进行分析,只需要在服务器端进行,因为通过浏览器地址栏中输入url地址,对服务器进行http请求的时候,浏览器会给服务器端发送一些请求报文,只要我们在服务器端通过程序将这个请求报文获取到,那么就可以知道这个请求报文的格式。
        示例程序:
        package main
        
        import (
            "fmt"
            "io"
            "net"
        )
        
        func main() {
            fmt.Println("服务端打开一个监听...")
            //监听
            listener, err := net.Listen("tcp", ":8080")
            if err != nil {
                fmt.Println("Listen err=", err)
                return
            }
        
            defer listener.Close()
        
            fmt.Println("服务端创建一个连接...")
            //阻塞,等待用户的连接
            conn, err1 := listener.Accept()
            if err1 != nil {
                fmt.Println("Accept err1=", err1)
                return
            }
            defer conn.Close()
        
            fmt.Println("服务端准备开始读取浏览器请求数据...")
            buf := make([]byte, 1024)
            for {
                n, err2 := conn.Read(buf)
                if err2 != nil {
                    if err2 == io.EOF {
                        fmt.Println("数据接收完毕!")
                    } else {
                        fmt.Println("Read err2=", err2)
                    }
                    return
                }
                fmt.Printf("#浏览器请求报文为:\n%v#", string(buf[:n]))
            }
        }
        
        此时在开启服务器端的程序,并在浏览器中输入http://localhost:8080,则可以看到在服务器端输出结果为:
        浏览器中执行:
        
        此时服务器端输出:
        
        2) 请求报文格式说明:
        HTTP 请求报文由请求行、请求头部、空行、请求包体4个部分组成,如下图所示:
        
         注:上述的程序演示中没有请求包体。
            ①. 请求行:
            请求行由方法字段、URL 字段 和HTTP 协议版本字段 3 个部分组成,他们之间使用空格隔开。常用的 HTTP 请求方法有 GET、POST。
                (1) GET:
                    1) 当客户端要从服务器中读取某个资源时,使用GET 方法。GET 方法要求服务器将URL 定位的资源放在响应报文的数据部分,回送给客户端,即向服务器请求某个资源。
                    2) 使用GET方法时,请求参数和对应的值附加在 URL 后面,利用一个问号(“?”)代表URL 的结尾与请求参数的开始,传递参数长度受限制,因此GET方法不适合用于上传数据。
                    3) 通过GET方法来获取网页时,参数会显示在浏览器地址栏上,因此保密性很差。
                 
                (2) POST:
                    1) 当客户端给服务器提供信息较多时可以使用POST 方法,POST 方法向服务器提交数据,比如完成表单数据的提交,将数据提交给服务器处理。
                    2) GET 一般用于获取/查询资源信息,POST 会附带用户数据,一般用于更新资源信息。POST 方法将请求参数封装在HTTP 请求数据中,而且长度没有限制,因为POST携带的数据,在HTTP的请求正文中,以名称/值的形式出现,可以传输大量数据。
            ②. 请求头部:
            请求头部为请求报文添加了一些附加信息,由“名/值”对组成,每行一对,名和值之间使用冒号分隔。请求头部通知服务器有关于客户端请求的信息,典型的请求头有:
            请求头    含义
            User-Agent    请求的浏览器类型
            Accept    客户端可识别的响应内容类型列表,星号“ * ”用于按范围将类型分组,用“ */* ”指示可接受全部类型,用“ type/* ”指示可接受 type 类型的所有子类型
            Accept-Language    客户端可接受的自然语言
            Accept-Encoding    客户端可接受的编码压缩格式
            Accept-Charset    可接受的应答的字符集
            Host    请求的主机名,允许多个域名同处一个IP 地址,即虚拟主机
            connection    连接方式(close或keepalive)
            Cookie    存储于客户端扩展字段,向同一域名的服务端发送属于该域的cookie
            ③. 空行:
            最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。
            ④. 请求包体:
            请求包体不在GET方法中使用,而是POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求包体相关的最常使用的是包体类型Content-Type和包体长度Content-Length。
    Ø 响应报文分析:
        1) 响应报文:
        想要知道响应报文格式并对其进行分析,应需要在客户端进行,上一个例子我们是通过在浏览器地址栏中输入url地址,对服务器进行http的请求,浏览器会给服务器端发送一些请求报文,只要我们在服务器端通过程序将这个请求报文获取到,那么就可以知道这个请求报文的格式。同样的道理,我们通过程序代码来模拟一个客户端程序,来实现对服务器的请求,而不是通过浏览器直接的对服务器进行请求,此时我们只需要知道我们之前通过浏览器发送给服务器的请求信息,对其进行包装发送即可。之前请求的信息为:
        
        我们需要将请求信息复制出来,然后组成一个字符串信息,需要注意的是上图中的请求信息每一行末尾都一个回车符和换行符,即\r\n,请求头最后一行后面是空行,空行是两个回车符和两个换行符\r\n\r\n,即在组合字符串的时候需要将组合在字符串中。
        //定义一个请求的字符串requstHeader 
        requstHeader := "GET /go HTTP/1.1\r\nHost: localhost:8080\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.8\r\n\r\n"
        注意,上面的字符串中的/go表示请求的路径,这个与服务器端中指定的路径要一致,否则就会报404错误。
        
        • 测试服务器端代码:
            package main
            
            import (
                "fmt"
                "net/http"
            )
            
            //服务器端编写的业务逻辑处理程序
            func myHandler(w http.ResponseWriter, r *http.Request) {
                fmt.Println(w, "hello world")
            }
            func main() {
                //可以理解HandleFunc是一个钩子函数,路径"/go"表示请求的路径
                //参数myHandler是一个函数类型,表示这个请求要处理的事情
                http.HandleFunc("/go", myHandler)
            
                //指定的地址进行监听,开启一个HTTP
                http.ListenAndServe("127.0.0.1:8080", nil)
            }
        
        • 客户端代码:
            package main
            
            import (
                "fmt"
                "io"
                "net"
            )
            
            func main() {
                conn, err := net.Dial("tcp", ":8080")
                if err != nil {
                    fmt.Println("Dial err=", err)
                    return
                }
                //释放conn连接
                defer conn.Close()
            
                //定义一个请求的字符串
                requstHeader := "GET /go HTTP/1.1\r\nHost: localhost:8080\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.8\r\n\r\n"
            
                //发送请求包,服务器才会响应包
                conn.Write([]byte(requstHeader))
            
                //定义一个切片,用来接收服务器的响应报文
                buf := make([]byte, 1024*2)
                n, err1 := conn.Read(buf)
                if err1 != nil {
                    if err1 == io.EOF {
                        fmt.Println("响应读取完毕!")
                    } else {
                        fmt.Println("Read err=", err1)
                    }
                    return
                }
            
                //将获取的响应信息转化为字符串并打印
                fmt.Printf("接收到的响应信息是:#\n%v#", string(buf[:n]))
            
            }
            
            分别打开服务器端和客户端执行命令行,
            服务器端:
            
            客户端:
            
        
            假如此时,将请求字符串中的/go,更改为其它的路径,比如改为/golang,但是此时服务器端的路径没有改,仍然为/go,则再执行客户端的时候,就会报404错误信息:
            
        2) 响应报文格式说明:
        HTTP 响应报文由状态行、响应头部、空行、响应包体4个部分组成,如下图所示:
        
         
            ①. 状态行:
            状态行由 HTTP 协议版本字段、状态码和状态码的描述文本3个部分组成,他们之间使用空格隔开。
            状态码:状态码由三位数字组成,第一位数字表示响应的类型,常用的状态码有五大类如下所示:
            状态码    含义
            1xx    表示服务器已接收了客户端请求,客户端可继续发送请求
            2xx    表示服务器已成功接收到请求并进行处理
            3xx    表示服务器要求客户端重定向
            4xx    表示客户端的请求有非法内容
            5xx    表示服务器未能正常处理客户端的请求而出现意外错误
             
            常见的状态码举例:
            状态码    含义
            200 OK    客户端请求成功
            400 Bad Request    请求报文有语法错误
            401 Unauthorized    未授权
            403 Forbidden    服务器拒绝服务
            404 Not Found    请求的资源不存在
            500 Internal Server Error    服务器内部错误
            503 Server Unavailable    服务器临时不能处理客户端请求(稍后可能可以)
            ②. 响应头部:
            响应头可能包括:
            响应头    含义
            Location    Location响应报头域用于重定向接受者到一个新的位置
            Server    Server 响应报头域包含了服务器用来处理请求的软件信息及其版本
            Vary    指示不可缓存的请求头列表
            Connection    连接方式
            ③. 空行:
            最后一个响应头部之后是一个空行,发送回车符和换行符,通知服务器以下不再有响应头部。
            ④. 响应包体:
            服务器返回给客户端的文本信息。
            
        

三、HTTP编程:

    Ø http服务器编程:
    服务器端代码:
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    //w,表示给客户端回复数据
    //r,表示读取客户端发送的数据
    func HandConn(w http.ResponseWriter, r *http.Request) {
        fmt.Println("执行客户请求的连接...")
        w.Write([]byte("hello go")) //给客户端回复数据
        fmt.Println("客户请求处理完毕!")
    }
    
    func main() {
        fmt.Println("Server starting...")
        //注册处理函数,用户连接,自动调用指定的处理函数
        http.HandleFunc("/go", HandConn)
    
        //监听绑定
        http.ListenAndServe(":8080", nil)
    }
    
    
    以上的服务器端的代码想实现的功能是,在服务器端对本机的8080端口进行监听,并注册了一个处理函数HandConn,指定的路劲是/go;当服务器端启动的时候,打开浏览器并在地址栏中输入http://localhost:8080/go,此时在浏览器中就会回显hello go
    打开服务器端的程序:
    
    打开浏览器并在地址栏中输入http://localhost:8080/go:
    
    此时浏览器处理完成:
    
    
    拓展了解:
    上述中的两个函数
        1) func ListenAndServe(addr string, handler Handler) error
        ListenAndServe会监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。handler参数一般会设为nil,此时会使用DefaultServeMux。
        即,函数ListenAndServe()使用指定的监听地址和处理器启动一个HTTP服务端。
        2) func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
        HandleFunc注册一个处理器函数handler和对应的模式pattern(注册到DefaultServeMux)。ServeMux的文档解释了模式的匹配机制。
        
        
    函数func HandConn(w http.ResponseWriter, r *http.Request)中的两个参数,分别表示:
        w,表示给客户端回复数据
        r,表示读取客户端发送的数据
    通过w和r,我们可以分别向客户端响应数据,和获取客户端的请求信息。
    程序代码:
    func HandConn(w http.ResponseWriter, r *http.Request) {
        fmt.Println("执行客户请求的连接...")
        w.Write([]byte("hello go")) //给客户端回复数据
        fmt.Println("r.Method=", r.Method)
        fmt.Println("r.RemoteAddr=", r.RemoteAddr)
        fmt.Println("r.URL=", r.URL)
        fmt.Println("r.RequestURI=", r.RequestURI)
        fmt.Println("r.Header=", r.Header)
        fmt.Println("r.Body=", r.Body)
        fmt.Println("客户请求处理完毕!")
    }
    打印结果:
    
    
    Ø http客户端编程:
    客户端代码:
    package main
    import (
        "fmt"
        "io"
        "net/http"
    )
    
    func main() {
        //指定需要获取的信息的网址,注意网址需要添加"http://",否则会报错
        //返回的值resp是一个*Response结构体类型的指针类型变量
        resp, err := http.Get("http://www.baidu.com")
        if err != nil {
            fmt.Println("http.Get err=", err)
            return
        }
    
        defer resp.Body.Close()
        fmt.Println("Stuatus=", resp.Status)        //返回状态码及描述信息
        fmt.Println("StatusCode=", resp.StatusCode) //返回状态码
        fmt.Println("Header=", resp.Header)         //返回响应头信息
        //    fmt.Println("Body=", resp.Body)
    
        //因为响应体是resp.Body是一个io.ReadCloser接口类型的变量,该接口是一个聚合了基本的io读取和关闭操作的接口
        //所以欲获取响应体中的信息需要使用io中的Read()方法来实现
        buf := make([]byte, 4*1024)
        var tmp string
        for {
            count += 1
            n, err := resp.Body.Read(buf)
            if err != nil {
                if err == io.EOF {
                    fmt.Println("数据读取完毕!")
                } else {
                    fmt.Println("resp.Body.Read err=", err)
                }
                break //结束for循环 (注意:不能使用return)
            }
            tmp += string(buf[:n]) //将获取的字符串进行拼接
        }
        fmt.Println("tmp=", tmp) //输出获取的百度网页信息
    }
    
    
    上述的几个函数用法介绍:
        1) func (*Client) Get 函数:
        func (c *Client) Get(url string) (resp *Response, err error)
        Get向指定的URL发出一个GET请求。
        响应信息变量resp是一个*Response结构体类型的指针类型变量:
        
        2) type ReadCloser 接口:
        type ReadCloser interface {
Reader
Closer

}

        ReadCloser接口聚合了基本的读取和关闭操作。
    需要注意的是,如果采用了for循环,循环的结束需要使用break,而不能使用return。因为,break结束了循环之后还会执行循环语句之后的代码,而return会将整个函数结束,循环之后的代码当然也是执行不到的。

image

目录
相关文章
|
1月前
|
JSON 编解码 Go
Golang中http编程
Golang中http编程
26 2
|
10月前
|
移动开发 应用服务中间件 Linux
35.从入门到精通:Python CGI编程 什么是CGI 网页浏览 CGI架构图 Web服务器支持及配置 第一个CGI程序 HTTP头部
35.从入门到精通:Python CGI编程 什么是CGI 网页浏览 CGI架构图 Web服务器支持及配置 第一个CGI程序 HTTP头部
|
10月前
|
C#
【C#编程最佳实践 二十二】如何发送带有重试机制的Http请求
【C#编程最佳实践 二十二】如何发送带有重试机制的Http请求
113 0
【C#编程最佳实践 二十二】如何发送带有重试机制的Http请求
|
12月前
|
JSON 监控 Java
【Java技术指南】「Unirest编程专题」一起认识一下一个“灰常”优秀的Http工具,让Http开发变得如此简单
Unirest-Java是一个轻量级的HTTP客户端库,它提供了简单易用的API,可以帮助Java开发人员快速地发送HTTP请求和处理响应。在本文中,我们将深入探讨Unirest-Java的技术细节和使用方法。
205 1
|
存储 数据采集 网络协议
Linux网络原理与编程(2)——第十二节 应用层协议(以HTTP为例)
协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的。更准确点来说,收发是按照比特位的形式进行的。
179 0
Linux网络原理与编程(2)——第十二节 应用层协议(以HTTP为例)
|
存储 Go 网络架构
Go HTTP 编程 | 02 - net/http 包剖析
Go HTTP 编程 | 02 - net/http 包剖析
Go HTTP 编程 | 02 - net/http 包剖析
|
Go 网络架构
Go HTTP 编程 | 01 - 使用 http 包创建 Web 服务器
Go HTTP 编程 | 01 - 使用 http 包创建 Web 服务器
Go HTTP 编程 | 01 - 使用 http 包创建 Web 服务器
http详解笔记学习9-socket编程怎么发送get请求-1
http详解笔记学习9-socket编程怎么发送get请求-1
74 0
http详解笔记学习9-socket编程怎么发送get请求-1
|
中间件 Go 网络架构
Go Web 编程入门:HTTP 自定义路由
Go 语言提供功能丰富的 net/http,实现了基础的 HTTP 中的 client 和 server 功能。在这一篇文章也有介绍一个基础的 HelloWorld 应用。
Go Web 编程入门:HTTP 自定义路由
WebAPI-HTTP编程模型
它是什么?它包含什么?它能干什么?
119 0