所有文章

golang web开发

编写一个基于TLS的WEB服务器。

创建一个服务端

package main

func main() {
	// 创建一个logger,这里只作为示例,该步骤不是必须的
	loger = log.New(os.Stdout, "", log.LstdFlags)
	
	// 创建服务并启动,第三个参数如果不指定,Server内部的错误信息就直接输出到stderr
	s := &http.Server{Addr: ":443", Handler: buildMux(), ErrorLog: loger}
	// 指定证书文件并启动服务
	go s.ListenAndServeTLS("/opt/ssl/cert.pem", "/opt/ssl/privkey.pem")

	// 再创建一个监听在80端口的Server,它负责把80上的所有请求重定向到443端口
	s80 := &http.Server{Addr: ":80", Handler: http.HandlerFunc(redirect80), ErrorLog: ctx.GetLogger()}
	go s80.ListenAndServe()

	// 创建一个channel,用来监听kill信号
	stop := make(chan string, 1)
	defer close(stop)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
	<-stop
	
	// 监听到信号后执行一系列回收资源等动作
	s.Shutdown(context.Background())
	s80.Shutdown(context.Background())
}

redirect80函数

func redirect80(w http.ResponseWriter, r *http.Request) {
	target := "https://" + r.Host + r.URL.Path
	if len(r.URL.RawQuery) > 0 {
		target += "?" + r.URL.RawQuery
	}

	http.Redirect(w, r, target, http.StatusTemporaryRedirect)
}

URI匹配规则

下面代码是向ServerMux注册一个Handler:

mux.HandleFunc("/users", user)

理想情况是当访问/users/jack时执行user函数,而实事上你会得到一个404,它只能通过/users被访问,为了达到理想结果,正确的写法应该是下面这样:

mux.HandleFunc("/users/", user)

这样一来,当我们访问/users/jack时,服务器会先匹配/users/jack,如果没有该路径则会查找/users/,如果还没有匹配到则查找/,如果还没有就404。

参数解析规则

使用curl命令发送请求:

curl -d 'name=jack' -d 'age=18' localhost/users?phone=123
  1. 当没有指定请求头时,默认使用Content-Type: application/x-www-form-urlencoded,服务器会依此来对收到的数据进行不同处理。
  2. 当指定了-d参数时,curl默认使用POST方法提交请求,所有-d提交的值会被用&符号连接在一起并放在请求体中提交。
  3. 使用-d时,参数中所有的\r\n都会被删除,除非使用--data-binary代替。

使用go解析参数:

func (m *Manager) myHandler(w http.ResponseWriter, r *http.Request) {
	println(r.FormValue("name")) // jack
	println(r.PostFormValue("phone")) // 空

	resume, _ := ioutil.ReadAll(r.Body)
	println(string(resume)) // user=jack&age=18
}
  1. go中有2个函数用于获取参数,FormValue()用于解析url和body中的参数,PostFormValue()只解析body中的参数而不解析url中的参数
  2. 当客户端设置Content-Type的值为application/jsonapplication/octet-stream等等时,上面两个函数都不会解析body中的参数,会得到一个空字符串。

请求体只能被读取一次

有时候我们会写这样的代码:

func user(w http.ResponseWriter, r *http.Request) {
	// 先测试一下r.Body中有没有值
	body := ioutil.ReadAll(r.Body)
	println(body)
	
        method := r.Method
        if method == POST {
		user := r.FormValue("user")
                println(user)
        }
}

然后发起请求:

curl -H "Content-Type: application/x-www-form-urlencoded" -d 'user=jack' localhost/users/

结果是在第一次获取请求体时没问题,但r.FormValue("user")返回的值为空,为什么?这是因为r.Body本身是个输入流,即然是流当然是有指针的,当第一次从流中读完数据后,流中的指针已经指向了流的最后,当执行r.FormValue("user")的时候,它会触发r.ParseForm()函数,进而再次从r.Body中读取数据并解析到map中供我们使用,但这时流中已经没有数据可以读了,自然也就得不到正确的结果。


编写日期:2017-12-27