-
Notifications
You must be signed in to change notification settings - Fork 0
/
frontAndBehindInteract
550 lines (495 loc) · 26 KB
/
frontAndBehindInteract
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
本篇讲一下如何写代码利用http/https协议进行前后端交互
*.写在前头: 针对的是http1.1, 虽说现在已经有http/2和http/3了
我们从广义到狭义一点点的来展开
1.首先, http协议的通信从大的角度上来看包含两部分:
a.客户端发出的请求
b.服务端返回的响应
2.再来看一下请求与响应的大体结构,其实很类似,单从结构上讲可以说是一模一样:
a.请求的结构:
请求行(由三部分组成: 请求方法 资源地址 http版本号)
请求头(多个 键:值 对组成)
空行(由回车和换页组成)
[body]
b.响应的结构:
状态行(同样由三部分组成: http版本号 状态码 与状态码对应的消息)
响应头(多个 键:值 对组成)
空行(由回车和换页组成)
[body]
*.需要注意的一点: 虽然请求头和响应头都可以添加为任何的键值对,但有些内置的键值对是有固定作用的, 你自己自定义的头肯定是用于你的逻辑中, 而有些内置的字段是给浏览器看的, 浏览器会根据这些键相应的值做出相应的反应
*.无论是请求中的body还是响应中的body都是可选的, 这个body在文档中被描述为entity,即实体,其中请求头和响应头中还有一些固定的字段是与body相关联的, 目的是告诉对方如何处理这个body.
参考地址:
https://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7 //该地址主要是描述与body相关的请求头/响应头都有啥
https://en.wikipedia.org/wiki/HTTP //向下拉有关于http1.1各部分的举例描述
=====
有了上面的知识做铺垫, 下面我们就具体的讲一下前端是 如何构建请求 的以及 后端是如何构建响应 的.
a.前端如何构建请求:
主要讲利用GET和POST方法请求时传递参数,文本,html,json,文件.其实客户端不仅可以使用内置的那几种请求方法, 还可以自己自定义, 只要后端有相应的处理逻辑即可.
GET主要是用于向服务器请求资源
POST主要是用于向服务器发送资源
-----客户端与服务端互发文本-----
*.该小节实用性不大,更倾向于测试的目的,因为通常情况下响应中返回的内容为json或html. 为了完成完整的请求与响应,所以请求阶段与响应阶段都使用了最简单的形式, 服务端输出资源路径,/who,及参数name='张三' age=18,客户端输出服务端的返回值"ok"
方法一: 利用XMLHttpRequst构建请求
//XMLHttpRequest mdn文档: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
00000客户端代码00000
<html>
<script>
xhr = new XMLHttpRequest()
//资源路径,/who,和参数连接在url后面
xhr.open("GET", "http://192.168.1.207:8000/who?name='张三'&age=18")
xhr.onload=function(){
//当服务端返回信息后,以弹出警告框的形式将返回的信息显示出来, 当前例子中,服务端返回的是字符串"ok"
alert(xhr.response) //xhr.response返回的内容类型可以是多种, 具体是啥依赖于其responseType属性,如果省略,则默认为text
}
xhr.send()
</script>
<html>
package main
import (
"net/http"
"fmt"
)
00000服务端代码00000
func main(){
http.HandleFunc("/who", func(w http.ResponseWriter, r *http.Request){
//解析请求中问题后面的参数
r.ParseForm()
//加上该头是因为客户端请求地址为/tmp/1.html,而请求的资源地址为192.168.1.207,跨域了,加上该头就能忽略这个问题,这是测试,生产环境不要这么用
w.Header().Set("Access-Control-Allow-Origin", "*")
//给客户端返回字符串"ok",意思是告诉客户端我收到了你的请求了
fmt.Fprintln(w, "ok")
//至于收到的请求信息对不对还要通过把资源路径以及参数打印出来才能确定,所以又加了两条输出语句
fmt.Println(r.URL.Path)
fmt.Println(r.Form)
})
http.ListenAndServe(":8000", nil)
}
方法二: 利用Request()构造器构建请求,配合fetch().then()使用
//Request()构造器 mdn链接: https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
00000客户端代码00000
<html>
<script>
fetch('http://192.168.1.207:8000/who?name="张三"&age=20',{method:"GET"}) // Replace with your API endpoint. //或是先使用let r = new Request('http://192.168.1.207:8000/who?name="张三"&age=20',{method:"GET"}),然后将r作为参数传给fetch, fetch(r)
.then(response => {
if (response.ok) {
//response是Response类型,使用chromium调试时使用点后没有text()函数,所以还是要去文档找到Response类型的定义以及看看都有哪些函数可调,除了text()外, 还有一个函数比较常>用就是json() 参考链接: https://developer.mozilla.org/en-US/docs/Web/API/Response
return response.text(); // Get the response body as text
} else {
throw new Error('Request failed');
}
})
.then(data => {
//console.log(data); // Log the response text
alert(data)
})
.catch(error => {
console.error(error); // Handle any errors
});
</script>
<html>
00000服务端代码00000
同方法一中的服务端代码
方法三: 利用html的表单构建请求
00000客户端代码00000
<html>
<body>
<form action="http://192.168.1.207:8000/who" enctype="x-www-form-urlencoded" method="GET">
<input type="text" name="name" value="张三">
<input type="text" name="age" value=18>
<input type="submit" value="提交">
</form>
</body>
<html>
00000服务端代码00000
同方法一中的服务端代码
00000客户端代码00000
*.为了单独处理反回的内容,使用和之前类似的场景, 所以又重新实现了submit类型按钮,使返回的内容以alert的形式出现,这里要注意的是submit类型的按钮是点击是触发了form表单的submit事件,所以这里有两种实现,一种是重写submit按钮的click事件,然后在里面第一句就是调用preventDefault()即阻止触发表单的submit事件,下面代码就是这么做的.还有一种方法是直接重写form的submit事件,只需要将submit.onclick改为form.onsubmit即可,后面函数里面的代码原样即可,其实只是preventDefault()函数表达的意思不同的,对于前者它阻止的是触发表单的submit事件; 对于后者它阻止的是form表单submit事件的默认行为(提交表单以及刷新页面)
<html>
<body>
<form id="f" action="http://192.168.1.207:8000/who">
<input type="text" name="name" value="张三">
<input type="text" name="age" value=18>,
<input type="submit" id="s" value="提交">
</form>
</body>
<script>
let xhr = new XMLHttpRequest()
let submit = document.getElementById("s")
let form = document.getElementById("f")
let data = new FormData(form)
xhr.onload = function(){
alert(xhr.response)
<!--
为了保持和上面一致还可在alert生效前将form隐藏,如果直接在alert函数前给表单加上隐藏属性是不能实现在alert出现前就隐藏表单效果的,因为你给元素设置属性后,浏览器更新表单是需要一定时间的,而你在给表单加上属性后立即就执行了alert函数,此时浏览器还没机会去渲染表单,所以要将alert函数异步执行并且要设置一定的delay:
form.setAttribute("hidden", true)
setTimeout(function(){alert(xhr.response)},1000) //此时delay为1秒,虽然这样处理不优雅,但目前也没找到更合适的办法
-->
}
submit.onclick = function(event){
event.preventDefault()
//这句要注意,请求方法不能用GET,因为文档明确说了, 如果用GET方法,则send()请求时是不会带上body的,参考链接:https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/send
xhr.open("POST", "http://192.168.1.207:8000/who")
xhr.send(data)
}
</script>
<html>
00000服务端代码00000
在上面服务端代码的基础上将r.ParseForm改为r.ParseMultipartForm(100) //之所以改解析方法,原因是客户端是使用POST方法请求的,参数被放在body中发过来的, 而官文中关于ParseForm()函数的说明也明确指明" when the Content-Type is not application/x-www-form-urlencoded, the request Body is not read", 而客户端使用POST发时,是使用multipart/form-data进行编码的,因此不能使用ParseForm()而应使用ParseMultipartForm(N) 参考链接: https://pkg.go.dev/net/http@go1.20.4#Request.ParseForm
方法四(如何利用纯nodejs实现):
1.下载xmlhttprequest模块, 然后模拟xhr请求,具体略
2.利用nodejs自带的http/https模块 下面只是一个简单的例子,不做具体的学习了,以后有需求再说细看吧
00000客户端代码00000
const http = require('http')
http.get('http://192.168.1.207:8000/who?name="张三"&age=21', (resp)=>{
let data = ''
resp.on('data', (chunk) => {
data += chunk
})
resp.on('end', () => {
console.log(data)
})
})
00000服务端代码00000
用上面哪个ParseForm和ParseMultipartForm哪个版本都行
-----------------------------
客户端与服务端互发json
00000客户端代码00000
<html>
<script>
let obj = {a:"1",b:"2",c:"3"}
let str_json = JSON.stringify(obj)
let xhr = new XMLHttpRequest()
xhr.onload = function(){
//let value = JSON.parse(xhr.response)
alert(xhr.response)
}
xhr.open('POST',"http://192.168.1.207:8000/who")
xhr.setRequestHeader('Content-Type', 'application/json')
//当跨域请求资源时,先会发送一个OPTIONS请求,待服务端通过后,才会发送真正的请求: https://stackoverflow.com/questions/8153832/xmlhttprequest-changes-post-to-option
xhr.send(str_json)
</script>
</html>
00000服务端代码00000
package main
import (
"net/http"
"fmt"
"encoding/json"
"io"
)
type Alphabet struct{
A string `json:"a"`
B string `json:"b"`
C string `json:"c"`
}
func main(){
http.HandleFunc("/who", func(w http.ResponseWriter, r *http.Request){
//加上该头是因为客户端请求地址为/tmp/1.html,而请求的资源地址为192.168.1.207,跨域了,加上该头就能忽略这个问题,这是测试,生产环境不要这么用
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers","*")
//之所以要加一个方法判断,是因为测试环境存在跨域问题, 而跨域问题会先发送一个OPTIONS方法的请求, 通过服务端返回的Access-Control-Allow-* 头判断服务端是否接受请求,服务端返回相应头之后, 才会发送正式的请求,所以将逻辑放到了if判断的语句体中
if (r.Method=="POST"){
var al Alphabet
//从请求中将body部分读出来,这个函数对初学者很重要, 即用它把body从整个请求信息中提取出来
body, _ := io.ReadAll(r.Body)
//解析使用json编码的数据,并将相应字段赋给Alphabet类型变量al
json.Unmarshal(body,&al)
fmt.Println(al)
//客户端发过来的是json,本例中咱也给客户端返回一个json
res_json := Alphabet{A:"4",B:"5",C:"6"}
json_encode, _ := json.Marshal(res_json)
w.Header().Add("Content-Type", "application/json")
fmt.Fprintln(w, string(json_encode))
//至于收到的请求信息对不对还要通过把资源路径以及参数打印出来才能确定,所以又加了两条输出语句
fmt.Println(r.URL.Path)
fmt.Println(r.Form)
}
})
http.ListenAndServe(":8000", nil)
}
有时客户端会将json进行base64编码后发送, 此时后端就要先将base64编码的字符串解码为字节类型的json,然后再用json.Unmarshal()进行解码以及后续处理,下面代码无前端交互,仅使用golang进行练习,而前端要将json进行base64编码往往还要依赖外部的js库,自带的btoa对于unicode字符好像编码不了:
package main
import (
"encoding/json"
"fmt"
"encoding/base64"
)
func main(){
var src []byte = []byte(`{ //json包的文档有说对于直接使用[]byte进行编码的Marshal()得到的是base64格式的编码
"Name":"Adam Ng",
"Age":36,
"Job":"CEO"
}
`)
b,_ := json.Marshal(src)
var f interface{}
json.Unmarshal(b, &f)
fmt.Println(f)
var base64string string
switch v:=f.(type){
case string:
base64string = v
default:
fmt.Println("what?")
}
var data interface{}
decodedBytes,_ := base64.StdEncoding.DecodeString(base64string)
json.Unmarshal(decodedBytes,&data)
m := data.(map[string]interface{})
for k,v := range m {
switch vv:=v.(type){
case string:
fmt.Println(k,"is string",vv)
case float64:
fmt.Println(k,"is float64",vv)
}
}
}
-----------------------------
*.由于安全问题,貌似js不能直接读取本地文件,必须通过html标签中转一下
单个文件(form版)
00000客户端代码00000
<html>
<body>
<form action="http://192.168.1.207:8000/who" method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
</body>
</html>
00000服务端代码00000
package main
import (
"net/http"
"fmt"
"os"
"io"
"path"
)
func main(){
http.HandleFunc("/who", func(w http.ResponseWriter, r *http.Request){
//加上该头是因为客户端请求地址为/tmp/1.html,而请求的资源地址为192.168.1.207,跨域了,加上该头就能忽略这个问题,这是测试,生产环境不要这么用
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers","*")
if (r.Method=="POST"){
//解析body填充FormFile
r.ParseMultipartForm(32<<20)
//取出文件
file,handler,_ := r.FormFile("file")
//创建空文件
f1,_ := os.Create("/tmp/"+handler.Filename)
//将body写入刚刚创建的空文件中
io.Copy(f1, file)
//读取一个本地文件
returnFile, _ := os.Open("/root/成都.webm")
//分隔文件完整路径,拿到文件名
fileName := path.Base(returnFile.Name())
//告知客户端返回的文件类型,以及如何呈现该文件,attachment表示直接以文件的形式下载下来
w.Header().Add("Content-Disposition", "attachment; filename="+fileName)
w.Header().Add("Content-Type", "application/octet-stream")
defer returnFile.Close()
//将读取的本地文件流写入w
io.Copy(w, returnFile)
//至于收到的请求信息对不对还要通过把资源路径以及参数打印出来才能确定,所以又加了两条输出语句
fmt.Println(r.URL.Path)
fmt.Println(r.Form)
}
})
http.ListenAndServe(":8000", nil)
}
单个文件(XMLHttpRequest版) //注意响应头中有中文的处理方法,代码中都有备注
*.手动下载返回的文件,而非让浏览器根据Content-Disposition自动触发,主要是改客户端代码,服务端代码不用动
00000客户端代码00000
<html>
<body>
<form id="f">
<input type="file" name="file" >
<input type="button" id="bu" value="提交">
</form>
</body>
<script>
let form = document.getElementById("f")
let button = document.getElementById('bu')
let data = new FormData(form)
let xhr = new XMLHttpRequest()
xhr.responseType='blob'
xhr.onload = function(){
alert(xhr.getAllResponseHeaders())
let url= URL.createObjectURL(xhr.response)
let link = document.createElement('a')
link.href=url
let filename = xhr.getResponseHeader("Content-Disposition").match("filename=(.*)")[1]
//将使用utf8编码的字符串进行解码
link.download=decodeURIComponent(filename)
link.click()
URL.revokeObjectURL(url)
}
bu.onclick = function(){
xhr.open("POST", "http://192.168.1.207:8000/who?name='李四'&age=21")
xhr.send(data)
}
</script>
</html>
00000服务端代码00000
package main
import (
"net/http"
"net/url"
"fmt"
"os"
"io"
"path"
)
func main(){
http.HandleFunc("/who", func(w http.ResponseWriter, r *http.Request){
//加上该头是因为客户端请求地址为/tmp/1.html,而请求的资源地址为192.168.1.207,跨域了,加上该头就能忽略这个问题,这是测试,生产环境不要这么用
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers","*")
w.Header().Set("Access-Control-Expose-Headers","*")
if (r.Method=="POST"){
//解析body填充FormFile
r.ParseMultipartForm(32<<20)
//取出文件
file,handler,_ := r.FormFile("file")
//创建空文件
f1,_ := os.Create("/tmp/"+handler.Filename)
//将body写入刚刚创建的空文件中
io.Copy(f1, file)
//读取一个本地文件
returnFile, _ := os.Open("/root/成都.webm")
//分隔文件完整路径,拿到文件名
fileName := path.Base(returnFile.Name())
//告知客户端返回的文件类型,以及如何呈现该文件,attachment表示直接以文件的形式下载下来
//成其要注意的是http headers一般是用ascii进行编码,所以如果headers中有中文的话, 需要先将中文使用utf8进行编码, 返回到前端后, 再由前端进行解码,golang中对中文进行utf8编码用的是url包中的QueryEscape()方法,而客户端js部分解码用的是decodeURIComponent()函数, 解码时要注意,比如后端编码时是对 "成都.webm"进和行编码,此时其实ascii部分是不变的,即".webm"不动,只对"成都进行编码",所以编码后的内容为"%E6%88%90%E9%83%BD.webm",而客户端使用decodeURIComponent()进行解码时也一样,你把整个字符串喂给它就行,不用先将未被编码的".webm"去掉.
w.Header().Add("Content-Disposition", "attachment; filename="+url.QueryEscape(fileName))
w.Header().Add("Content-Type", "application/octet-stream")
defer returnFile.Close()
//将读取的本地文件流写入w
io.Copy(w, returnFile)
//至于收到的请求信息对不对还要通过把资源路径以及参数打印出来才能确定,所以又加了两条输出语句
fmt.Println(r.URL.Path)
fmt.Println(r.Form)
}
})
http.ListenAndServe(":8000", nil)
}
单个文件(fetch()...then()版)
00000客户端代码00000
<html>
<body>
<form id="f">
<input type="file" name="file">
<input type="button" id="bu" value="提交">
</form>
<script>
let form = document.getElementById('f');
let button = document.getElementById('bu');
button.onclick = function() {
let data = new FormData(form);
let r = new Request("http://192.168.1.207:8000/who", { method: "POST", body: data });
fetch(r)
.then(response => {
let CD = response.headers.get("Content-Disposition");
//这句要注意,因为下个then要用到, 所以这里的文件名必须整成一个最上层的变量,要不下个then()函数中引用不到
filename = decodeURIComponent(CD.match("filename=(.*)")[1]);
//注意,现在不知道怎么单独构建一个Blob类型的对象, 但官方提供了一个blob()方法,虽然返的是Promise对象, 但该对象中值为Blob类型,所以我们就可以在下一个then中使用这
个Blob类型的变量了
return response.blob();
})
.then(blob => {
let url = URL.createObjectURL(blob);
let link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
});
};
</script>
</body>
</html>
00000服务端代码00000
同xmlhttprequest版本的服务端代码
多个文件(fetch()...then()版) //fetch()...then()要配合Request()使用,Request()构造器去构造实际的请求信息, fetch()相当于xhr.send(),只不过fetch()返回的是一个Promise对象
00000客户端代码00000
<html>
<body>
<form id="f">
<input type="file" name="file" multiple>
<input type="button" id="bu" value="提交">
</form>
<script>
let form = document.getElementById('f');
let button = document.getElementById('bu');
button.onclick = function() {
let data = new FormData(form);
let r = new Request("http://192.168.1.207:8000/who", { method: "POST", body: data });
fetch(r)
.then(response => {
let CD = response.headers.get("Content-Disposition");
//这句要注意,因为下个then要用到, 所以这里的文件名必须整成一个最上层的变量,要不下个then()函数中引用不到
filename = decodeURIComponent(CD.match("filename=(.*)")[1]);
//注意,现在不知道怎么单独构建一个Blob类型的对象, 但官方提供了一个blob()方法,虽然返的是Promise对象, 但该对象中值为Blob类型,所以我们就可以在下一个then中使用这
个Blob类型的变量了
return response.blob();
})
.then(blob => {
let url = URL.createObjectURL(blob);
let link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
});
};
</script>
</body>
</html>
00000服务端代码00000
package main
import (
"net/http"
"net/url"
"fmt"
"os"
"io"
"path"
)
func main(){
http.HandleFunc("/who", func(w http.ResponseWriter, r *http.Request){
//加上该头是因为客户端请求地址为/tmp/1.html,而请求的资源地址为192.168.1.207,跨域了,加上该头就能忽略这个问题,这是测试,生产环境不要这么用
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers","*")
w.Header().Set("Access-Control-Expose-Headers","*")
if (r.Method=="POST"){
//解析body填充FormFile
r.ParseMultipartForm(32<<20)
//取出文件
files := r.MultipartForm.File["file"]
//创建空文件
for _, file := range files {
f,_ := os.Create(file.Filename)
//这一句是重点, 拿到符合io.Copy()方法第二个参数类型的值
f1,_ := file.Open()
//将body写入刚刚创建的空文件中
io.Copy(f, f1)
f.Close()
}
//读取一个本地文件
returnFile, _ := os.Open("/root/成都.webm")
//分隔文件完整路径,拿到文件名
fileName := path.Base(returnFile.Name())
//告知客户端返回的文件类型,以及如何呈现该文件,attachment表示直接以文件的形式下载下来
//成其要注意的是http headers一般是用ascii进行编码,所以如果headers中有中文的话, 需要先将中文使用utf8进行编码, 返回到前端后, 再由前端进行解码,golang中对中文进行utf8编码用的是url包中的QueryEscape()方法,而客户端js部分解码用的是decodeURIComponent()函数, 解码时要注意,比如后端编码时是对 "成都.webm"进和行编码,此时其实ascii部分是不变的,即".webm"不动,只对"成都进行编码",所以编码后的内容为"%E6%88%90%E9%83%BD.webm",而客户端使用decodeURIComponent()进行解码时也一样,你把整个字符串喂给它就行,不用先将未被编码的".webm"去掉.
w.Header().Add("Content-Disposition", "attachment; filename="+url.QueryEscape(fileName))
w.Header().Add("Content-Type", "application/octet-stream")
defer returnFile.Close()
//将读取的本地文件流写入w
io.Copy(w, returnFile)
//至于收到的请求信息对不对还要通过把资源路径以及参数打印出来才能确定,所以又加了两条输出语句
fmt.Println(r.URL.Path)
fmt.Println(r.Form)
}
})
http.ListenAndServe(":8000", nil)
}
写在最后: 其实每种情况都想着用N种方法去实现, 但写到最后就麻烦了,后面就是只要实现就完事了,每种情况里需要注意的事项都已写在注释中了,所以要仔细看注释