创建一个简单的http服务
使用go语言搭建一个http服务事件很简单的事情,如果代码逻辑很简单的话甚至不用框架就行。话不多说,直接看代码吧。
package main
import (
"fmt"
"net/http"
)
func echo(rw http.ResponseWriter, r *http.Request) {
var rspText string
params := r.URL.Query() // 获取查询参数
values, ok := params["name"]
if ok && len(values) > 0 {
rspText = fmt.Sprintf("hello, %s!", values[0])
} else {
rspText = "hello, boy!"
}
rw.Header().Add("Content-Type", "text/plain") // 添加header,值得注意的是,header必须在Write之前调用,否则不会生效
rw.WriteHeader(http.StatusOK) // 这里可以省略
rw.Write([]byte(rspText))
}
func main() {
http.HandleFunc("/echo", echo) // 注册路由以及回调函数
// 监听ip和端口,第二个参数为nil,则使用默认的DefaultServeMux来处理请求
if err := http.ListenAndServe("127.0.0.1:10001", nil); err != nil {
panic(err)
}
}
运行以及返回结果
~ http :10001/echo name==coolboy -v
GET /echo?name=coolboy HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:10001
User-Agent: HTTPie/2.0.0
HTTP/1.1 200 OK
Content-Length: 15
Content-Type: text/plain
Date: Wed, 19 Jan 2022 08:00:36 GMT
hello, coolboy!
可看出go语言创建http服务最简单只需要两步
- 使用
http.HandleFunc
绑定路由以及路由对应的回调函数 - 使用
http.ListenAndServe
指定监听端口,并启动服务
下面我们第一步的去分析这两个函数。
了解http.HandleFunc
首先我们先看下源码,go语言的源码注释还是可以的,借助源码我们可以方便正确的了解该函数的作用
// file: net/http/server.go
var defaultServeMux ServeMux
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
// 这里可以看出,实际上是调用了DefaultServeMux.HandleFunc
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
首先看参数类型,第一个是路由地址,没啥好说的。不过注释提示了,如果想进一步了解路由的匹配规则,可以查看ServeMux
的文档。第二个参数也限定了回调函数的类型。下面我们具体查看下回调函数的这两个参数类型。
首先是ResponseWriter
,它其实是一个接口类型。看起来也是简单且清晰的,就是三件事三个接口
- 写入http response status状态码
- 写入http response header
- 写入http response body(这里使用byte类型,即支持任意数据类型)
type Header map[string][]string
// 注意,源码注释比较详细,但是字数太多就不放这里了,推荐去看看的
type ResponseWriter interface {
// 返回一个header字典,调用WriteHeader函数时,将会返回该map数据。但是对header的
// 修改必须在调用WriteHeader前,否则不会生效
Header() Header
// 写数据到http响应里的body中
// 调用该函数之前,如果没调用WriteHeader则会先调用一次
Write([]byte) (int, error)
// 先http相应写入statusCode以及header内容
WriteHeader(statusCode int)
}
下面再来看看Request
,它是一个结构体,而不像ResponseWriter
是一个接口类型。
type URL struct {
Scheme string
Opaque string // encoded opaque data
User *Userinfo // username and password information
Host string // host or host:port
Path string // path (relative paths may omit leading slash)
RawPath string // encoded path hint (see EscapedPath method)
ForceQuery bool // append a query ('?') even if RawQuery is empty
RawQuery string // encoded query values, without '?'
Fragment string // fragment for references, without '#'
RawFragment string // encoded fragment hint (see EscapedFragment method)
}
type Request struct {
// http请求方法:如GET, POST, PUT
Method string
// 如上面URL所示,记录了请求url以及url后面的查询参数信息
URL *url.URL
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
// If a server received a request with header lines,
//
// Host: example.com
// accept-encoding: gzip, deflate
// Accept-Language: en-us
// fOO: Bar
// foo: two
//
// then
//
// Header = map[string][]string{
// "Accept-Encoding": {"gzip, deflate"},
// "Accept-Language": {"en-us"},
// "Foo": {"Bar", "two"},
// }
Header Header
// Body is the request's body.
//
// For client requests, a nil body means the request has no
// body, such as a GET request. The HTTP Client's Transport
// is responsible for calling the Close method.
// 这里由提示,如果由使用Body,则要记得调用Close方法关闭Body
Body io.ReadCloser
GetBody func() (io.ReadCloser, error)
// ContentLength records the length of the associated content.
// The value -1 indicates that the length is unknown.
// Values >= 0 indicate that the given number of bytes may
// be read from Body.
//
// For client requests, a value of 0 with a non-nil Body is
// also treated as unknown.
ContentLength int64
// TransferEncoding lists the transfer encodings from outermost to
// innermost. An empty list denotes the "identity" encoding.
// TransferEncoding can usually be ignored; chunked encoding is
// automatically added and removed as necessary when sending and
// receiving requests.
TransferEncoding []string
Close bool
Host string
// Form contains the parsed form data, including both the URL
// field's query parameters and the PATCH, POST, or PUT form data.
// This field is only available after ParseForm is called.
// The HTTP client ignores Form and uses Body instead.
// 注意的是,这里的值包括url和body里的两部分
Form url.Values
// PostForm contains the parsed form data from PATCH, POST
// or PUT body parameters.
// This field is only available after ParseForm is called.
// The HTTP client ignores PostForm and uses Body instead.
PostForm url.Values
MultipartForm *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string
TLS *tls.ConnectionState
Cancel <-chan struct{}
// Response is the redirect response which caused this request
// to be created. This field is only populated during client
// redirects.
Response *Response
ctx context.Context
}
可以看出,Request
结构包含了http请求的信息并做了简单的处理,同时也内置了一些基础的函数,大家可以通过IDE的智能提示或者源码看看。
了解ListenAndServe
ListenAndServe
函数主要是指定监听的ip和端口,并指定一个Handler
处理监听的请求。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
Handler
也是一个接口类型,但是Handler
里的ServeHTTP(ResponseWriter, *Request)
的参数类型是不是很熟悉,跟我们上面路由回调函数一模一样,这两者之间是不是有啥不可告人的事呢?
Server
涉及到http服务器的处理细节,我们先不细究,主要还是看看Handler
。
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
上面是Server
处理http请求的一个关键代码,可以看到如果handler
为空,则会使用DefaultServeMux
来代替,那么我们下面就来仔细探究DefaultServeMux
。请看代码:
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry // 这里就是路由映射表
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
// 这里就是路由以及对应的路由回调函数
type muxEntry struct {
h Handler
pattern string
}
// ServeMux的一些关键函数
// 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.Handle(pattern, HandlerFunc(handler))
}
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// 判断路由不能为空,以及不能重复设置
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
// 将路由及回调函数保存到路由映射表中
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
// 这个函数的作用,就是请求url(路由)匹配一个handler(路由回调函数)
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// 不支持 http.method == CONNECT
if r.Method == "CONNECT" {
if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
return mux.handler(r.Host, r.URL.Path)
}
// 获取host和path
// 比如url="https://www.baidu.com:8080/hello?a=b
// 则 host = www.baidu.com
// path = /hello
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
// 打个比方,如过请求 https://baidu.com/aaa 没有匹配成功
// 则重定向到 https://baidu.com/aaa/
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
// 这里的path跟URL里的path不一致,则重定向到URL里的path去
if path != r.URL.Path {
_, pattern = mux.handler(host, path)
url := *r.URL
url.Path = path
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
return mux.handler(host, r.URL.Path)
}
// 其实这个才是真正的路由匹配函数,注意它跟Handler首字母大小写不同
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
// 这里大致可以看出。先使用完全匹配,然后在按照顺序使用前缀匹配
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
// 这里就是上面Server部分所说,当一个http请求进来时,默认使用DefaultServeMux
// 从而进入到这个函数来。
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
// 根据路由规则获取对应路由回调函数
h, _ := mux.Handler(r)
// 执行路由回调函数
h.ServeHTTP(w, r)
}
另一种写法
上面里的main
函数可以换成下面写法,两者是等价的
func main() {
server := http.ServeMux{}
server.HandleFunc("/echo", echo) // 注册路由以及回调函数
// 监听ip和端口,第二个参数为nil,则使用默认的DefaultServeMux来处理请求
if err := http.ListenAndServe("127.0.0.1:10001", &server); err != nil {
panic(err)
}
}
进一步思考
通过上面的源码,我可以可以发现go语言更多的是定义http处理相关接口,比如上面的ServeMux
,它负责将路由映射到处理函数中。实际上我们完成可以自己写一个,只要实现相关的接口就ok。
这里是不是可以看出go语言的一些哲学呢?比如提供接口,让用户去决定具体实现?(原谅我知识面不够,不知道怎么说...)
最后我们想一想,利用这些接口,我们是不是可以开发一个简单的go框架呢,比如说最简单的MVC模型框架?我倒是挺想试试的!