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
- cmd 中:
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>