首先提出一些概念上的笔记
第一个Hello world
package main
import "net/http"
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world"))
})
http.ListenAndServe("localhost:8000", nil) // DefaultServeMux
}
创建Web Server
-
http.ListenAndServe()-
第一个参数时网络地址
- 如果为
"",那么就是所有网络接口的80端口
- 如果为
-
第二个参数是
handler- 如果为
nil,那么就是DefaultServeMux[DefaultServeMux是一个multiplexer(可以看作是路由器)]
- 如果为
-
-
http.Server这是一个struct-
Addr字段表示网络地址- 如果为"",那么就是所有网络接口的
80端口
- 如果为"",那么就是所有网络接口的
-
Handler字段- 如果为
nil,那么就是DefaultServeMux
- 如果为
-
ListenAndServe()函数
-
Handler
-
handler是一个接口(interface) -
handler定义了一个方法ServeHTTP()- HTTPResponseWriter
- 指向Request这个
struct的指针
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
DefaultServeMux
- 他是一个
Multiplexer(多路复用器) - 他也是一个
Handler
多个Handler-http.Handle
-
不指定
Server struct里面的Hnadler字段值 -
可以使用
http.Handle将某个Handler附加到DefaultServeMuxhttp包有一个Handle函数ServerMux struct也有一个Handle方法
-
如果你调用
http.Handle,实际上调用的是DefaultServeMux上的Handle方法DefaultServeMux就是ServerMux的指针变量
htttp.Handle
func Handle(pattern string, handler Handler)
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler函数-http.HandleFunc
-
Handler函数就是那些行为与handler类似的函数 -
Handler函数的签名与ServeHttp方法的签名一样,接收- 一个
http.ResponseWriter - 一个指向
http.Request的指针
- 一个
http.HandleFunc原理
golang有一个函数类型:HandlerFunc。可以将某个具体适当签名的函数f,适配称为一个Hnadler,而这个Handler具有方法f。- 第二个参数是一个
Handler函数 - 内部调用的还是
http.Handle函数 - 源码
// HandleFunc registers the handler function for the given pattern(模式)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handler(pattern, HandlerFunc(handler))
}
http.HandlerFunc可以把Handler函数转化为Handlerhttp.HandlerFunc相关源码
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers.
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
内置的Handlers
http.NotFoundHandler
-
func NotFoundHandler() Handler // 返回一个handler,它给每个请求的响应都是"404 page not found"
http.RedirectHandler
-
func RedirectHandler(url string, code int) Handler // 返回一个handler,它把每个请求使用给定的状态码转跳到指定的URLurl:要跳转到的URLcode:跳转的状态码(3xx),常见的:StatusMovedPermanently、StatusFound或StatusSeeOther等
http.StripPrefix
-
func StripPrefix(prefix string, h handler) Handler // 返回一个handler,它从请求URL中去掉指定的前缀,然后再调用另一个handler- 如果请求的
URL与提供的前缀不符,那么404
- 如果请求的
-
略像中间件
prefix,URL将要被移除的字符串前缀h,是一个handler,在移除字符串前缀之后,这个handler将会接收到请求- 修饰了另一个
Handler
http.TimeoutHandler
-
func TimeoutHandler(h Handler, dt time.Duration, msg string) // 返回一个handler,它用来在指定时间内运行传入的h -
也相当于是一个修饰器
h,将要被修饰的handlerdt,第一个handler允许的处理时间msg,如果超时,那么把msg返回给请求,表示响应时间过长
http.FileServer
-
func FileServer(root FileSystem) Handler // 返回一个handler,使用基于root的文件系统来响应请求 -
type FileSystem interface { Open(name string) (File, error) } -
使用时需要用到操作系统的文件系统,所以还需委托给:
type Dir string-
func (d Dir) Open(name string) (File, error)
example.before:
package main
import "net/http"
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
http.ServeFile(w, r, "rootname" + r.URL.Path)
})
http.ListenAndServe(":8000", nil)
}
example.after:
package main
import "net/http"
func main() {
http.ListenAndServe(":8000", http.FileServer(http.Dir("rootname")))
}
HTTP消息
-
HTTP Request和HTTP Response(请求和响应) -
它们具有相同的结构:
- 请求(响应)行
- 0个或多个
Header - 空行
- 可选的消息行(
Body)
-
net/http包提供了用于表示HTTP消息的结构
- 请求(
Request)
-
Request(是个struct),代表客户端发送的HTTP请求消息 -
重要字段:
-
URL-
代表了请求行(请求信息第一行)里面的部分内容
-
URL字段是指向url.URL类型的一个指针,url.URL是一个struct:type URL struct { Scheme string Opaque string User *Userinfo Host string Path string RawQuery string Fragment string }-
通用格式是:
scheme://[userinfo@]host/path[?query][#fragment] -
不可以斜杠开头的
URL被解释为:scheme:opaque[?query][#fragment] -
RawQuery会提供实际查询的字符串example:
http://www.example.com/post?id=1&pw=1-
它的
RawQuery的值就是id=1&pw=1- 还有一个简便方法可以得到
Key-Value对:通过Request的Form字段
- 还有一个简便方法可以得到
-
-
如果从浏览器发出的请求,那么将无法提取出
Fragment字段的值- 浏览器在发送请求时会把
fragment部分去掉
- 浏览器在发送请求时会把
-
但不是所有请求都是从浏览器发出的(例如从HTTP客户端包)
-
r.URL.Query()会提供查询字符串对应的map[string][]stringexample:
url := r.URL query := url.Query() // map[string][]string id := query["id"] // []string{"123"} threadID := query.Get("thread_id") // "456" 重复的话返回的是第一个值
-
-
-
Header- 请求和响应(
Request、Response)的headers是通过Header类型来描述的,它是一个map,用来表述HTTP Header里的Key-Value对。 Header map的key是string类型,value是[]string- 设置
key的时候会创建一个空的[]string作为value,value里面第一个元素就是新header的值 - 为指定的
key添加一个新的header值,执行append操作即可
example
-
r.Header- 返回
map
- 返回
-
r.Header["Accept-Encoding"]- 返回:
[gzip, deflate]([]string类型)
- 返回:
-
r.Header.Get("Accept-Encoding")- 返回:
gzip, deflate(string类型)
- 返回:
- 请求和响应(
-
Body-
请求和响应的
bodies都是使用Body字段来表示的 -
Body是一个io.ReadCloser接口- 一个
Reader接口 - 一个
Closer接口
- 一个
-
Reader接口定义了一个Open方法- 参数:
[]byte - 返回:
byte的数量、可选的错误
- 参数:
-
Closer接口定义了一个Close方法- 没有参数,返回可选的错误
-
想要读取请求
body的内容,可以调用Body的Read方法
-
-
Form、PostForm、MultipartForm-
来自表单的Post请求例子
<form action="/porcess" method="post" enctype="application/x-www-form-urlencoded"> <input type="text" name="first_name" /> <input type="text" name="last_name" /> <input type="submit" /> </form>- 这个
HTML表单里面的数据会以name-value对的形式,通过POST请求发送出去 - 它的数据内容会放到
POST请求的Body里面 - 通过
POST发送的name-value数据对的格式可以通过表单的Content Type来指定,也就是enctype属性
- 这个
-
表单的
enctype属性-
默认值是:
application/x-www-form-urlencoded -
浏览器被要求至少要支持:
application/x-www-form-urlencoded和multipart/form-dataHTML5的话,还需要支持text/plain
-
如果
enctype是application/x-www-form-urlencoded,那么浏览器会将表单数据编码到查询字符串里面。例如first_name=sau%20skjkd&last_name=skdjfk -
如果
enctype是multipart/form-data,那么- 每一个
name-value对都会被转换为一个MME消息部分 - 每一个部分都有自己的
Content Type和Content Disposition
- 每一个
-
选择
- 简单文本:表单
URL编码 - 大量数据,例如上传文件:
multipart-MIME(甚至可以把二进制数据通过选择Base64编码,来当作文本进行发送)
- 简单文本:表单
-
表单的
GET-
通过表单的
method属性,可以设置POST还是GETexample:
<form action="/process" method="get"> <input type="text" name="first_name" /> <input type="text" name="last_name" /> <input type="submit" /> </form> -
GET请求没有Body,所有的数据都通过URL的name-value对来发送
-
-
Form字段-
Request上的函数允许我们从URL或/和Body中提取数据,通过这些字段:-
From源码
Form url.Values type Values map[string][][string] -
PostForm- 如果表单和
URL里有同样的key,那么它们都会放在一个slice里:表单里的值靠前,URL的值靠后 - 如果只想要表单的
key-value对,不要URL的,可以使用PostForm字段 PostForm只支持application/x-www-form-urlencoded
- 如果表单和
-
MultipartForm-
想要得到
multipart key-value对,必须使用MultipartForm字段 -
想要使用
MultipartForm这个字段的话,首先需要调用ParseMultipartForm这个方法- 该方法会在必要的时候调用
ParseForm方法 - 参数是需要读取数据的长度(长度是字节数)
- 该方法会在必要的时候调用
-
MultipartForm只包含表单的key-value对 -
返回类型是一个
struct而不是map。这个struct里有两个mapkey是string,value是[]string
-
MultipartReader-
func (r *Request) MultipartReader() (*multipart.Reader, error) // 如果是multipart/form-data或multipart混合的POST请求 // MultipartReader返回一个MIME multipart reader // 否则返回nil和一个错误 -
可以使用该函数代替
ParseMultipartForm来把请求的body作为stream进行处理- 不是把表单作为一个对象来处理的,不是一次性获得整个
map - 逐个检查来自表单的值,然后每次处理一个
- 不是把表单作为一个对象来处理的,不是一次性获得整个
-
-
-
-
Form里面的数据是key-value对 -
通常的做法是:
- 先调用
ParseForm或ParseMultipartForm来解析Request - 然后相应的访问
Form、PostForm或MultipartForm字段
- 先调用
-
FormValue方法会返回Form字段中指定key对应的第一个value- 无需调用
ParseForm或ParseMultipartForm
- 无需调用
-
PostFormValue方法也一样,但只能读取PostForm -
FormValue和PostFormValue都会调用ParseMultipartForm方法 -
但如果表单的
enctype设为mutipart/form-data,那么即使你调用ParseMultipartForm方法,也无法通过FormValue获得想要的值 -
文件与
JSON-
上传文件
-
multipart/form-data最常见的应用场景就是上传文件- 调用
ParseMultipartForm方法 - 从
File字段获得FileHeader,调用其Open方法来获得文件 - 可以使用
ioutil.ReadAll函数把文件内容读取到byte切片里
- 调用
-
FormFile方法- 无需调用
ParseMultipartForm方法 - 返回指定
key对应的第一个value - 同时返回
File和FileHeader,以及错误信息 - 如果只上传一个文件,那么这种方式会快一些
- 无需调用
-
-
POST请求-JSON Body-
不是所有的
POST请求都来自Form -
客户端框架(例如Angular等)会以不同的方式对
POST请求编码jQuery通常使用application/x-www-form-urlencodedAngular是application/json
-
parseForm方法无法处理application/json
-
-
-
-
-
-
-
也可以通过
Request的方法访问请求中的Cookie、URL、User Agent等信息
ResponseWriter
源码
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}
-
Handler中的ServeHTTP(w ResponseWriter, r *Request)两个都是引用传递值 -
从服务器向客户端返回响应需要使用
ResponseWriter -
ResponseWrier是一个接口,handler用它来返回响应 -
真正支撑
ResponseWriter的幕后struct是非导出的http.response -
写入到
ResponseWriterWrite方法接收一个byte切片作为参数,然后把它写入到HTTP响应的Body里面- 如果在
Write方法被调用时,header里面没有设定content type,那么数据的前512字节就会被用来检测content type
-
WriteHeader方法-
WriteHeader方法接收一个整数类型(HTTP状态码)作为参数,并把它作为HTTP响应的状态码返回 -
如果该方法没有显示调用,那么在第一次调用
Write方法前,会隐式的调用WriteHeader(http.StatusOK)- 所以
WriteHeader主要用来发送错误类的HTTP状态码
- 所以
-
调用完
WriteHeader方法之后,仍然可以写入到ResponseWriter,但无法再修改header
-
-
Header方法Header方法返回headers的map,可以进行修改- 修改后的
header将会体现在返回给客户端的HTTP响应里
根据上面的概念做了以上实验,成功运行啦!