golang web实验1 | 青训营

72 阅读9分钟

首先提出一些概念上的笔记

第一个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附加到DefaultServeMux

    • http包有一个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函数转化为Handler
  • http.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,它把每个请求使用给定的状态码转跳到指定的URL
    
    • url:要跳转到的URL
    • code:跳转的状态码(3xx),常见的:StatusMovedPermanentlyStatusFoundStatusSeeOther

http.StripPrefix

  • func StripPrefix(prefix string, h handler) Handler
    // 返回一个handler,它从请求URL中去掉指定的前缀,然后再调用另一个handler
    
    • 如果请求的URL与提供的前缀不符,那么404
  • 略像中间件

    • prefixURL将要被移除的字符串前缀
    • h,是一个handler,在移除字符串前缀之后,这个handler将会接收到请求
    • 修饰了另一个Handler

http.TimeoutHandler

  • func TimeoutHandler(h Handler, dt time.Duration, msg string)
    // 返回一个handler,它用来在指定时间内运行传入的h
    
  • 也相当于是一个修饰器

    • h,将要被修饰的handler
    • dt,第一个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 RequestHTTP Response(请求和响应)

  • 它们具有相同的结构:

    • 请求(响应)行
    • 0个或多个Header
    • 空行
    • 可选的消息行(Body
  • net/http包提供了用于表示HTTP消息的结构

  1. 请求(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对:通过RequestForm字段
        • 如果从浏览器发出的请求,那么将无法提取出Fragment字段的值

          • 浏览器在发送请求时会把fragment部分去掉
        • 但不是所有请求都是从浏览器发出的(例如从HTTP客户端包)

        • r.URL.Query()会提供查询字符串对应的map[string][]string

          example:

          url := r.URL
          query := url.Query() // map[string][]string
          id := query["id"] // []string{"123"}
          threadID := query.Get("thread_id") // "456" 重复的话返回的是第一个值
          
    • Header

      • 请求和响应(RequestResponse)的headers是通过Header类型来描述的,它是一个map,用来表述HTTP Header里的Key-Value对。
      • Header mapkeystring类型,value[]string
      • 设置key的时候会创建一个空的[]string作为valuevalue里面第一个元素就是新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的内容,可以调用BodyRead方法

    • FormPostFormMultipartForm

      • 来自表单的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-urlencodedmultipart/form-data

          • HTML5的话,还需要支持text/plain
        • 如果enctypeapplication/x-www-form-urlencoded,那么浏览器会将表单数据编码到查询字符串里面。例如

          first_name=sau%20skjkd&last_name=skdjfk
          
        • 如果enctypemultipart/form-data,那么

          • 每一个name-value对都会被转换为一个MME消息部分
          • 每一个部分都有自己的Content TypeContent Disposition
        • 选择

          1. 简单文本:表单URL编码
          2. 大量数据,例如上传文件:multipart-MIME(甚至可以把二进制数据通过选择Base64编码,来当作文本进行发送)
        • 表单的GET

          • 通过表单的method属性,可以设置POST还是GET

            example:

            <form action="/process" method="get">
                <input type="text" name="first_name" />
                <input type="text" name="last_name" />
                <input type="submit" />
            </form>
            
          • GET请求没有Body,所有的数据都通过URLname-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里有两个map

                • keystringvalue[]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

          • 通常的做法是:

            1. 先调用ParseFormParseMultipartForm来解析Request
            2. 然后相应的访问FormPostFormMultipartForm字段
          • FormValue方法会返回Form字段中指定key对应的第一个value

            • 无需调用ParseFormParseMultipartForm
          • PostFormValue方法也一样,但只能读取PostForm

          • FormValuePostFormValue都会调用ParseMultipartForm方法

          • 但如果表单的enctype设为mutipart/form-data,那么即使你调用ParseMultipartForm方法,也无法通过FormValue获得想要的值

          • 文件与JSON

            • 上传文件

              • multipart/form-data最常见的应用场景就是上传文件

                1. 调用ParseMultipartForm方法
                2. File字段获得FileHeader,调用其Open方法来获得文件
                3. 可以使用ioutil.ReadAll函数把文件内容读取到byte切片里
              • FormFile方法

                • 无需调用ParseMultipartForm方法
                • 返回指定key对应的第一个value
                • 同时返回FileFileHeader,以及错误信息
                • 如果只上传一个文件,那么这种方式会快一些
            • POST请求-JSON Body

              • 不是所有的POST请求都来自Form

              • 客户端框架(例如Angular等)会以不同的方式对POST请求编码

                • jQuery通常使用application/x-www-form-urlencoded
                • Angularapplication/json
              • parseForm方法无法处理application/json

  • 也可以通过Request的方法访问请求中的CookieURLUser 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

  • 写入到ResponseWriter

    • Write方法接收一个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方法返回headersmap,可以进行修改
    • 修改后的header将会体现在返回给客户端的HTTP响应里

0827.png

根据上面的概念做了以上实验,成功运行啦!