75142913在线留言
GO web笔记1:使用net/http包构建服务端_Go语言_网络人

GO web笔记1:使用net/http包构建服务端

Kwok 发表于:2020-11-15 14:15:54 点击:1 评论: 0

使用Go语言开发网站的时候我们会使用到内置的net/http包,该包提供了HTTP客户端和服务端的实现。这里我们主要使用该包提供的方法创建一个GO web的服务端

一、快速建立一个web服务端

构建服务端,我们使用到http.Handle方法,里面需要提供一个路由和一个处理的函数。提供的函数里面的参数是固定的,请看下面代码演示:

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

func index(w http.ResponseWriter, r *http.Request) {
	//Fprint() 与Fprintln() 按照默认格式将内容写入文件并返回内容是写入的字节与错误。
	len, err := fmt.Fprint(w, "w是响应写入,即返回给客户端的内容")
	if err != nil {
		fmt.Println("Fprint向w写入内容出错:", err)
	} else {
		fmt.Println("Fprint向w写入", len, "个字节")
	}
}
func main() {
	http.HandleFunc("/", index) //http根据用户请求路径绑定处理函数

	//下面使用匿名函数请求1个远程网页将在本地返回(类似代理)
	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		//先使用http.Get方法请求一个网页(爬虫)
		resp, err := http.Get("http://www.neter8.com/")
		if err != nil {
			fmt.Println("http.Get请求错误:", err)
		}
		defer resp.Body.Close() //客户必须在完成后关闭响应主体
		//读取resp.Body里的内容
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Println("ReadAll读取resp.Body失败:", err)
		}
		fmt.Fprintln(w, string(body)) //将网页内容转为
		//b, _ := ioutil.ReadFile("./1.txt")//读取本地文件
		//fmt.Fprint(w, string(b))//将文件里的内容解析
		//fmt.Fprintln(w, "请求路径:", html.EscapeString(r.URL.Path))
	})

	//监听并启动web服务
	err := http.ListenAndServe(":8000", nil)
	if err != nil {
		fmt.Printf("http 服务启动失败, 错误:%vn", err)
		return
	}
}

二、http客户端的简单介绍

Get,Head,Post和 PostForm 发出 HTTP(或HTTPS)请求: 

resp, err := http.Get("http://www.neter8.com/")//通过GET请求一个地址

//通过POST请求一个地址
resp, err := http.Post("http://www.neter8.com/", "image/jpeg", &buf)

//通过postForm向一个地方发送数据
resp, err := http.PostForm("http://www.neter8.com/",
	url.Values{"name": {"网络人"}, "url": {"neter8.com"}})

三、http.Handle 和 http.HandleFunc 区别

我们一般使用HandleFunc就可以了,如果想要使用handle相对来说就要麻烦一点,因为Handler的第二个参数是Handler这个的接口的ServeHTTP()方法。

func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

所以这个方法使用的时候需要自己去定义struct实现这个Handler接口。关于接口实现详细介绍:http://www.neter8.com/go/79.html

//定义一个httpServer的结构体
type myServer struct {
}

//实现接口ServeHTTP
func (myServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(r.URL.Path)) //使用ResponseWriter的Write方法把URL路径显示在网页上
}
func main() {
	var server myServer          //server的类型是myServer
	var mux = http.NewServeMux() //创建一个ServeMux实例
	mux.Handle("/", server)      //server实现了ServeHTTP方法即实现Handle的接口

	//启动web服务,如果遇到err输出错误日志并退出程序
	log.Fatal(http.ListenAndServe("localhost:8000", mux))
	//ListenAndServe第二个参数为mux即使用mux定义的内容处理请求
}

 四、深入了解http.Handle与ListenAndServe底层做了什么

下面我们重写Server来实现自己的相关功能。 

import (
	"io"
	"log"
	"net/http"
	"time"
)

type myHandle struct{}

func (*myHandle) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if h, ok := mux[r.URL.String()]; ok {
		h(w, r) //找到/hello并调用路由方法sayHello(w, r)
		return
	}
	io.WriteString(w, "没有绑定方法将执行这个,URL:"+r.URL.String()) //没有找到的路由只打印URL
}
func sayHello(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "sayHello(w,r):这是访问/hello得到的结果哦~")
}
func index(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "这是首页哦index(w,r)~")
}

var mux map[string]func(http.ResponseWriter, *http.Request) //定义一个map
func main() {
	server := http.Server{
		Addr:        ":8000",
		Handler:     &myHandle{},
		ReadTimeout: 5 * time.Second,
	}
	mux = make(map[string]func(http.ResponseWriter, *http.Request))
	mux["/hello"] = sayHello
	mux["/"] = index //路由绑定方法
	err := server.ListenAndServe()
	if err != nil {
		log.Fatal(err)
	}
}

五、利用http的静态文件映射做一个目录浏览功能

func main() {
	mux := http.NewServeMux()
	wd, err := os.Getwd() //获取当前目录
	if err != nil {
		log.Fatal(err) //读取目录出错
	}
	//Handle处理/static/路径时调用StripPrefix方法
	mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(wd))))
	//StripPrefix遇到/static/开始的地址返回 FileServer(http.Dir(wd))里的内容
	//http.FileServer(http.Dir("img/css")) 返回一个使用 FileSystem 接口 root 提供文件访问服务的 HTTP 处理器。可以方便的实现静态文件服务器。
	//http.Dir("img/css")实现了FileSystem 的接口Open方法,返回的是 http.Dir 类型,将字符串路径转换成文件系统。
	err = http.ListenAndServe(":8000", mux)
	if err != nil {
		log.Fatal(err)
	}
}

六、获取到用户请求信息

这里需要使用到 r *http.Request里的方法与字段获取到用户请求的相关header信息和通过PUT、POST提交的数据。

//getHead 获取请求头里的信息
func getHead(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "地址:", r.URL.Path)
	fmt.Fprintln(w, "?后面的查询字符串:", r.URL.RawQuery)
	fmt.Fprintln(w, "所有的Header头信息:", r.Header) //将返回一个map[string][]string类型
	fmt.Fprintln(w, "Accept-Encoding信息:", r.Header["Accept-Encoding"])
	fmt.Fprintln(w, "获取没有[]的值:", r.Header.Get("User-Agent")) //Get方法直接返回值
}

//getBody 获取请求体信息(用户POST过来的数据)
func getBody(w http.ResponseWriter, r *http.Request) {
	len := r.ContentLength    //获取请求体的长度
	body := make([]byte, len) //定义一个接收的切片
	r.Body.Read(body)         //将请求体里的内容读到body里
	fmt.Fprintln(w, "请求体中的内容:", string(body))
	r.ParseForm() //解析读取到的表单字段到r.Form与r.PostForm里
	//ur.Values类型是form解析好的表单数据,包括query参数和POST、PUT的表单数据
	fmt.Fprintln(w, "请求URL参数:", r.Form)      //r.Form返回map[string][]string类型GET的URL?参数
	fmt.Fprintln(w, "POST字段信息:", r.PostForm) //r.PostForm返回用户POST、PUT方法过来的字段
	//解析POST的指定字段、r.PostForm返回用户POST、PUT方法过来的字段
	fmt.Fprintln(w, "POST字段username:", r.PostForm["username"])
	//当表单enctype属性为multipart/form-data(文件上传)的时候需要使用MultiPartForm读取数据
	fmt.Fprintln(w, "接收multipart:", r.MultipartForm)
	//不需要执行r.ParseForm()就可以使用下面方
	fmt.Fprintln(w, "快速获取URL参数GET字段:", r.FormValue("username"))
	fmt.Fprintln(w, "快速获取POST的表单字段:", r.PostFormValue("username"))

}
func main() {
	http.HandleFunc("/getheadinfo", getHead)
	http.HandleFunc("/getbody", getBody)
	http.ListenAndServe(":8000", nil)
}

七、返回给用户的内容

上面已经使用了很多次的w http.ResponseWriter相关字段与方法,r是用户传过来的,w就是我们服务器代码给用户的数据。我们可以通过w设置并自定义哪些数据、以什么样的方法写入到用户的浏览器里。

//respJSON 返回自定义的header与内容
func respJSON(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")  //以json格式返回给浏览器
	w.Header().Set("Location", "http://www.neter8.com") //WriteHeader设置302、301将会跳转到url上
	w.Header().Set("Content-Length", "999")             //Content-Length不要设置啊,会导致空白页,此项由服务器自动计算长度
	w.WriteHeader(500)                                  //返回一个状态码:200是正常
	json, _ := json.Marshal(map[string]string{
		"func": "这里直接使用Write方法返回数据",
	}) //Marshal返回的是[]byte切片
	w.Write(json) //w.Write的接收一个[]byte切片哦~
}

func main() {
	//GO HTTP严格区分大小写/respJSON将返回404
	http.HandleFunc("/respjson", respJSON)
	http.ListenAndServe(":8000", nil)
}

八、GO内存加载配置

GO语言在IO请求和管理上太强大了,这也是我喜欢GO,并学习的驱动力。早期做PHP开发的时候没有缓存,由于PHP是解释型语言,每次运行都需要对代码解释一次,同时配置文件也会重新读取一次,为了应对大并发,减少IO我们通常把配置通过redis等内存型数据库保存到内存里读取使用。GO的优势在这里就可以充分发挥出来。

config, _ := ioutil.ReadFile("config.txt") //只在程序运行时读取一次并加载到内存后续所有的处理器(Handle)都可调用
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
	content, _ := ioutil.ReadFile("content.txt") //用户请求一次读一次,高并发时硬盘IO压力大
	content = append(content, config...)
	w.Write(content)
})

 

除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:http://www.neter8.com/go/92.html
标签:gowebhttp服务端Kwok最后编辑于:2020-11-25 18:25:09
0
感谢打赏!

《GO web笔记1:使用net/http包构建服务端》的网友评论(0)

本站推荐阅读

热门点击文章