diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts index e057b17..868a7de 100644 --- a/docs/.vuepress/config.ts +++ b/docs/.vuepress/config.ts @@ -68,6 +68,7 @@ const Navbar = [ ]; export default defineUserConfig({ + port: 8081, lang: "zh-CN", title: "瓢儿白施肥记", description: "记录一些学习内容", diff --git a/docs/backend/go/web/index.md b/docs/backend/go/web/index.md index d00299a..79a708e 100644 --- a/docs/backend/go/web/index.md +++ b/docs/backend/go/web/index.md @@ -335,7 +335,7 @@ URL Fragment Request Header -- 请求和相应(Request、Response)的 headers 是通过 Header 类型来描述的,它是一个 map,用来表述 HTTP Header 里的 Key-Value 对 +- 请求和响应(Request、Response)的 headers 是通过 Header 类型来描述的,它是一个 map,用来表述 HTTP Header 里的 Key-Value 对 - Header map 的 key 是 string 类型,value 是一个字符串切片 []string - 设置 key 的时候会创建一个空的 []string 作为 value,value 里面第一个元素就是新 header 的值 - 为指定的 key 添加一个新的 header 值,执行 append 操作即可 @@ -359,7 +359,7 @@ server.ListenAndServe() Request Body -- 请求和相应的 bodies 都是使用 Body 字段来表示的 +- 请求和响应的 bodies 都是使用 Body 字段来表示的 - Body 是一个 io.ReadCloser 接口 ```go @@ -421,3 +421,354 @@ URL Query server.ListenAndServe() ``` + +#### Form + +Request 上的函数允许从 URL 或 / 和 Body 中提取数据,通过如下字段 + +- Form +- PostForm +- MultipartForm +- FormValue +- PostFormValue +- FormFile +- MultiPartReader + +**Form 里面的数据是 key-value 对** + +通常的做法是: + +- 先调用 ParseForm 或 ParseMultipartForm 来解析 Request +- 然后相应地访问 Form、PostForm、MultipartForm 字段 + +```html +
+``` + +```go +server := http.Server{ + Addr: "localhost:8080", +} +http.HandleFunc("/process", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + fmt.Fprintln(w, r.Form) // map[email:[2439639832@qq.com] name:[客户1号]] +}) +server.ListenAndServe() +``` + +**PostForm 字段** + +- 上例中,如果只想得到 name 这个 Key 的 Value,可以使用 r.Form["name"],它返回含有一个元素的 slice:["客户 1 号"] +- 如果表单和 URL 里有同样的 Key,那么它们都会放在一个 slice 里:表单里的值靠前,URL 的值靠后 +- 如果只想要表单的 key-value 对,不要 URL 的,可以使用 PostForm 字段 + +```html {2} + +``` + +```go {6-7} +server := http.Server{ + Addr: "localhost:8080", +} +http.HandleFunc("/process", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + fmt.Fprintln(w, r.Form) // map[email:[2439639832@qq.com] name:[客户1号 客户2号]] + fmt.Fprintln(w, r.PostForm) // map[email:[2439639832@qq.com] name:[客户1号]] +}) +server.ListenAndServe() +``` + +**MultipartForm 字段** + +- PostForm 只支持 application/x-www-form-urlencoded 编码 +- 要想得到 multipart/form-data 对,必须使用 MultipartForm 字段 +- 要想使用 MultiPartForm 字段,必须先调用 ParseMultipartForm 方法 + - 该方法会在必要时调用 ParseForm 方法 + - 参数是需要读取数据的长度 +- MultipartForm 只包含表单的 key-value 对 +- 返回类型是一个 struct,这个 struct 里面有两个 map: + - key 是 string,value 是 []string + - key 是 string,value 是 文件 + +```html {4} + +``` + +```go {6} +server := http.Server{ + Addr: "localhost:8080", +} +http.HandleFunc("/process", func(w http.ResponseWriter, r *http.Request) { + r.ParseMultipartForm(1024) + fmt.Fprintln(w, r.MultipartForm) // &{map[email:[2439639832@qq.com] name:[客户1号]] map[]} +}) +server.ListenAndServe() +``` + +**FormValue 和 PostFormValue 方法** + +- FormValue 会返回 Form 字段中指定 key 对应的第一个 value + - 无需调用 ParseForm 或 ParseMultipartForm +- PostFormValue 也一样,但只能读取 PostForm 字段 +- FormValue 和 PostFormValue 都会调用 ParseMultipartForm 方法 + +**上传文件** + +multipart/form-data 最常见的应用场景就是上传文件 + +- 首先调用 ParseMultiPartForm 方法 +- 从 File 字段获得 FileHeader,调用其 Open 方法来获得文件 +- 可以使用 io.ReadAll 函数把文件内容读取到 byte 切片里 + +```html + +``` + +```go +func process(w http.ResponseWriter, r *http.Request) { + r.ParseMultipartForm(1024) + + fileHeader := r.MultipartForm.File["file"][0] + file, err := fileHeader.Open() + if err == nil { + data, err := io.ReadAll(file) + if err == nil { + fmt.Fprintln(w, string(data)) + } + } +} + +func main() { + server := http.Server{ + Addr: "localhost:8080", + } + + http.HandleFunc("/process", process) + + server.ListenAndServe() +} + +``` + +**FormFile 方法** + +- 上传文件还有一个简便方法:FormFile + - 无需调用 ParseMultipartForm 方法 + - 返回指定 key 对应的第一个 value + - 同时返回 File 和 FileHeader,以及错误信息 + - 如果只上传一个文件,那么这种方式会快一些 + +```go +func process(w http.ResponseWriter, r *http.Request) { + // r.ParseMultipartForm(1024) + + // fileHeader := r.MultipartForm.File["file"][0] + // file, err := fileHeader.Open() + + file, _, err := r.FormFile("file") + + if err == nil { + data, err := io.ReadAll(file) + if err == nil { + fmt.Fprintln(w, string(data)) + } + } +} +``` + +**MultiPartReader()** + +- func (r *Request) MultiPartReader() (*multipart.Reader, error) +- 如果是 multipart/form-data 或 multipart 混合的 POST 请求 + - MultiPartReader 方法会返回一个 MIME multipart reader + - 否则返回一个错误 +- 可以使用该函数代替 ParseMultipartForm,来把请求的 body 作为 stream 进行处理 + - 不是把表单作为一个对象来处理的,不是一次性获得整个 map + - 逐个检查来自表单的值,然后每次处理一个 + +#### POST 请求 - JSON Body + +- 不是所有的 POST 请求都来自 Form +- 客户端框架(例如 Angular 等)会议不同的方式对 POST 请求编码: + - jQuery 通常使用 application/x-www-form-urlencoded + - Angular 通常使用 application/json +- ParseForm 方法无法处理 application/json + +### 响应 + +#### ResponseWriter + +- 从服务器向客户端返回响应需要使用 ResponseWriter +- ResponseWriter 是一个接口,handler 用它来返回响应 +- 真正支撑 ResponseWriter 的幕后 struct 是非导出的 http.response + +**写入到 ResponseWriter** + +- Write 方法接收一个 byte 切片作为参数,然后把它写入到 HTTP 响应的 body 里 +- 如果在 Write 方法被调用时, header 里面没有设定 content-type,那么数据的前 512 字节就会用来被检测 content type + +```go +func writeExample(w http.ResponseWriter, r *http.Request) { + str := ` +