Golang http编程

web 工作流程

  • Web 服务器的工作原理可以简单地归纳为
    • 客户机通过 TCP/IP 协议建立到服务器的 TCP 连接
    • 客户端向服务器发送 HTTP 协议请求包,请求服务器里的资源文档
    • 服务器向客户机发送 HTTP 协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端
    • 客户机与服务器断开。由客户端解释 HTML 文档,在客户端屏幕上渲染图形结果

HTTP 协议

  • 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,它详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议
  • HTTP 协议通常承载于 TCP 协议之上

http 服务端

package main

import (
	"fmt"
	"net/http"
)

// /go handler
func goHandler(w http.ResponseWriter, r *http.Request) {
	// 获取远程地址
	fmt.Println(r.RemoteAddr, "连接成功")
	// 获取请求类型
	fmt.Println("请求方法: ", r.Method)
	// 获取url路径
	fmt.Println("url path: ", r.URL.Path)
	// 获取header数据
	fmt.Println("header: ", r.Header)
	// 获取请求body内容
	fmt.Println("body: ", r.Body)

	// 返回信息
	_, _ = w.Write([]byte("hello world"))
}

func main()  {
	// 单独写回掉函数
	http.HandleFunc("/go", goHandler)

	// 启动http监听
	_ = http.ListenAndServe("127.0.0.1:8000", nil)
}

http 包客户端

package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	resp, err := http.Get("http://127.0.0.1:8000/go")
	if err != nil {
		fmt.Println("获取数据失败, err: ", err)
		return
	}
	defer resp.Body.Close()
	// 获取http状态码
	fmt.Println("status: ", resp.Status)
	// 获取header信息
	fmt.Println("header: ", resp.Status)

	buf := make([]byte, 124)
	for {
		// 接收服务端数据
		n, err := resp.Body.Read(buf)

		fmt.Println(string(buf[:n]))

		if err == io.EOF {
			break
		}

		if err != nil {
			fmt.Println("数据读取失败, err: ", err)
			continue
		}
	}
}

tcp 包实现客户端

package main

import (
	"fmt"
	"io"
	"net"
)

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println("connect err: ", err)
		return
	}
	defer conn.Close()
	msg := "GET /go HTTP/1.1\r\n"
	msg += "Host: 127.0.0.1:8000\r\n"
	msg += "Connection: close\r\n"
	msg += "\r\n\r\n"

	_, err = io.WriteString(conn, msg)
	if err != nil {
		fmt.Println("write string failed, ", err)
		return
	}
	buf := make([]byte, 4096)
	for {
		n, err := conn.Read(buf)
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("recv data has err, ", err)
			break
		}
		fmt.Println(string(buf[:n]))
	}
}

其他错误

golang GET 出现 x509: certificate signed by unknown authority

我们编写一个 Go 程序来尝试与这个 HTTPS server 建立连接并通信。

//gohttps/4-https/client1.go
package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    resp, err := http.Get("https://localhost:8081")
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}

运行这个 client,我们得到如下错误:

$go run client1.go
error: Get https://localhost:8081: x509: certificate signed by unknown authority

此时服务端也给出了错误日志提示:

2015/04/30 16:03:31 http: TLS handshake error from 127.0.0.1:62004: remote error: bad certificate

显然从客户端日志来看,go 实现的 Client 端默认也是要对服务端传过来的数字证书进行校验的,但客户端提示:这个证书是由不知名 CA 签发 的!

我们可以修改一下 client1.go 的代码,让 client 端略过对证书的校验:

//gohttps/4-https/client2.go
package main

import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
)

func main() {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://localhost:8081")

    if err != nil {
        fmt.Println("error:", err)
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))

}

通过设置 tls.Config 的 InsecureSkipVerify 为 true,client 将不再对服务端的证书进行校验。执行后的结果 也证实了这一点:

$go run client2.go
Hi, This is an example of http service in golang!

http 请求设置 header

package main
import (
    "fmt"
    "io/ioutil"
    "net/http"
    "os"
    "encoding/json"
)

func main() { //生成client 参数为默认
    client := &http.Client{}
    //生成要访问的url
    url := "http://somesite/somepath/"
    //提交请求
    reqest, err := http.NewRequest("GET", url, nil)

    //增加header选项
    reqest.Header.Add("Cookie", "xxxxxx")
    reqest.Header.Add("User-Agent", "xxx")
    reqest.Header.Add("X-Requested-With", "xxxx")

    if err != nil {
        panic(err)
    }
    //处理返回结果
    response, _ := client.Do(reqest)
    defer response.Body.Close()
}

http 代理请求

package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"time"
)

// http proxy get方法
func httpProxyGet(dataUrl, proxyIp string) (data []byte,  err error) {
	transport := &http.Transport{
		TLSClientConfig:   &tls.Config{InsecureSkipVerify: true}, //ssl证书报错问题
		DisableKeepAlives: false,                                 //关闭连接复用,因为后台连接过多最后会造成端口耗尽
		MaxIdleConns:      -1,                                    //最大空闲连接数量
		IdleConnTimeout:   time.Duration(5 * time.Second),        //空闲连接超时时间
		Proxy: http.ProxyURL(&url.URL{
			Scheme: "http",
			Host:   proxyIp,
		}),
	}
	// 设置代理方式二
	//proxyUrl, _ := url.Parse("http://" + proxyIp)
	//transport.Proxy = http.ProxyURL(proxyUrl)

	// 创建http客户端
	client := &http.Client{
		Timeout:   time.Duration(30 * time.Second),
		Transport: transport,
	}

	request, err := http.NewRequest("GET", dataUrl, nil)
	if err != nil {
		return
	}
	// 请求数据
	resp, err := client.Do(request)
	if err != nil {
		err = fmt.Errorf("request %s, proxyIp: (%s),err: %v", dataUrl, proxyIp, err)
		return
	}
	defer resp.Body.Close()
	// 读取数据
	buf := make([]byte, 128)
	data = make([]byte, 0, 2048)
	for {
		n, err := resp.Body.Read(buf)
		data = append(data, buf[:n]...)

		if err == io.EOF {
			break
		}
		if err != nil {
			continue
		}
	}
	return
}


func main() {
	data, err := httpProxyGet("http://www.baidu.com/", "89.22.11.55:9000")
	if err != nil {
		println(err)
		return
	}
	fmt.Println(string(data))
}

websocket 编程

webSocket 是什么

  • WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议
  • WebSocket 使得客户端和服务器之间的数据交换变得更加简单, 允许服务端主动向客户端推送数据
  • 在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
  • 需要安装第三方包:
    • cmd 中:go get -u -v github.com/gorilla/websocket

webSocket 示例

服务端

package main

import (
	"github.com/gorilla/websocket"
	"log"
	"net/http"
)

var upgrader = websocket.Upgrader{	// 配置websocker选项
	// 允许websocket跨域
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

func echo(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()
	for {
		mt, message, err := c.ReadMessage()
		if err != nil {
			log.Println("read:", err)
			break
		}
		log.Printf("recv: %s", message)
		err = c.WriteMessage(mt, message)
		if err != nil {
			log.Println("write:", err)
			break
		}
	}
}
func main() {
	log.SetFlags(0)
	http.HandleFunc("/echo", echo)
	log.Fatal(http.ListenAndServe("127.0.0.1:8081", nil))
}

html 客户端

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>websocket demo</title>
  </head>
  <body>
    <table>
      <tr>
        <td valign="top" width="50%">
          <p>
            Click "Open" to create a connection to the server, "Send" to send a
            message to the server and "Close" to close the connection. You can
            change the message and send multiple times.
          </p>
          <form>
            <button id="open">Open</button>
            <button id="close">Close</button>
            <input id="input" type="text" value="Hello world!" />
            <button id="send">Send</button>
          </form>
        </td>
        <td valign="top" width="50%">
          <div id="output"></div>
        </td>
      </tr>

      <script>
        window.addEventListener('load', function(evt) {
          var output = document.getElementById('output')
          var input = document.getElementById('input')
          var ws
          var print = function(message) {
            var d = document.createElement('div')
            d.innerHTML = message
            output.appendChild(d)
          }
          document.getElementById('open').onclick = function(evt) {
            if (ws) {
              return false
            }
            ws = new WebSocket('ws://127.0.0.1:8081/echo')
            ws.onopen = function(evt) {
              print('OPEN')
            }
            ws.onclose = function(evt) {
              print('CLOSE')
              ws = null
            }
            ws.onmessage = function(evt) {
              print('RESPONSE: ' + evt.data)
            }
            ws.onerror = function(evt) {
              print('ERROR: ' + evt.data)
            }
            return false
          }
          document.getElementById('send').onclick = function(evt) {
            if (!ws) {
              return false
            }
            print('SEND: ' + input.value)
            ws.send(input.value)
            return false
          }
          document.getElementById('close').onclick = function(evt) {
            if (!ws) {
              return false
            }
            ws.close()
            return false
          }
        })
      </script>
    </table>
  </body>
</html>
posted @ 2020-03-17 11:59  ZhiChao&  阅读(609)  评论(0编辑  收藏  举报